From 9b400f9a9df0a73f690565505937329635d9cc9e Mon Sep 17 00:00:00 2001 From: Minglong Li <790669171@qq.com> Date: Thu, 4 Apr 2024 02:48:49 +0800 Subject: [PATCH] refactor: input --- .github/workflows/web.yaml | 2 +- .yarnrc | 2 + package.json | 8 +- src/components/canvas/contextmenu.tsx | 30 +- src/components/canvas/index.tsx | 211 +++--- src/components/canvas/widgets/start-cell.ts | 3 + src/components/canvas/widgets/text.ts | 38 +- src/components/root/index.tsx | 8 +- src/components/suggest/index.tsx | 29 +- src/components/textarea/component.tsx | 272 +++++++ src/components/textarea/cursor/cursor-info.ts | 46 +- src/components/textarea/defs/context.ts | 32 +- src/components/textarea/events/blur-event.ts | 3 - .../textarea/events/cursor-event.ts | 9 - src/components/textarea/events/index.ts | 2 - src/components/textarea/index.ts | 2 + src/components/textarea/index.tsx | 223 ------ src/components/textarea/input/index.ts | 8 - src/components/textarea/input/position.ts | 30 - src/components/textarea/input/selection.ts | 96 --- .../textarea/input/textarea-input-events.ts | 23 - .../textarea/input/textarea-input-host.ts | 17 - .../textarea/input/textarea-input.ts | 313 -------- .../textarea/input/textarea-state.ts | 188 ----- .../textarea/input/textarea-wrapper.ts | 66 -- src/components/textarea/input/type_data.ts | 6 - src/components/textarea/managers/cursor.ts | 228 ------ src/components/textarea/managers/index.ts | 6 - src/components/textarea/managers/input.ts | 189 ----- src/components/textarea/managers/selection.ts | 130 ---- src/components/textarea/managers/suggest.ts | 219 ------ src/components/textarea/store/cursor.spec.ts | 70 ++ src/components/textarea/store/cursor.ts | 227 ++++++ src/components/textarea/store/index.ts | 3 + src/components/textarea/store/selection.ts | 135 ++++ src/components/textarea/store/store.ts | 34 + src/components/textarea/store/suggest.ts | 172 +++++ src/components/textarea/store/text.spec.ts | 36 + .../textarea/{managers => store}/text.ts | 134 ++-- .../{managers => store}/token.test.ts | 0 .../textarea/{managers => store}/token.ts | 0 src/components/textarea/textarea.module.scss | 1 + src/core/events/keyboard.ts | 33 +- src/core/painter/box.ts | 5 + src/core/painter/canvas.ts | 1 - src/ui/contextmenu/index.tsx | 58 +- src/ui/dialog/index.tsx | 9 +- tsconfig.json | 2 +- vite.config.ts | 11 + yarn.lock | 680 +++++++++++++++++- 50 files changed, 1957 insertions(+), 2093 deletions(-) create mode 100644 .yarnrc create mode 100644 src/components/textarea/component.tsx delete mode 100644 src/components/textarea/events/blur-event.ts delete mode 100644 src/components/textarea/events/cursor-event.ts delete mode 100644 src/components/textarea/events/index.ts create mode 100644 src/components/textarea/index.ts delete mode 100644 src/components/textarea/index.tsx delete mode 100644 src/components/textarea/input/index.ts delete mode 100644 src/components/textarea/input/position.ts delete mode 100644 src/components/textarea/input/selection.ts delete mode 100644 src/components/textarea/input/textarea-input-events.ts delete mode 100644 src/components/textarea/input/textarea-input-host.ts delete mode 100644 src/components/textarea/input/textarea-input.ts delete mode 100644 src/components/textarea/input/textarea-state.ts delete mode 100644 src/components/textarea/input/textarea-wrapper.ts delete mode 100644 src/components/textarea/input/type_data.ts delete mode 100644 src/components/textarea/managers/cursor.ts delete mode 100644 src/components/textarea/managers/index.ts delete mode 100644 src/components/textarea/managers/input.ts delete mode 100644 src/components/textarea/managers/selection.ts delete mode 100644 src/components/textarea/managers/suggest.ts create mode 100644 src/components/textarea/store/cursor.spec.ts create mode 100644 src/components/textarea/store/cursor.ts create mode 100644 src/components/textarea/store/index.ts create mode 100644 src/components/textarea/store/selection.ts create mode 100644 src/components/textarea/store/store.ts create mode 100644 src/components/textarea/store/suggest.ts create mode 100644 src/components/textarea/store/text.spec.ts rename src/components/textarea/{managers => store}/text.ts (54%) rename src/components/textarea/{managers => store}/token.test.ts (100%) rename src/components/textarea/{managers => store}/token.ts (100%) create mode 100644 vite.config.ts diff --git a/.github/workflows/web.yaml b/.github/workflows/web.yaml index 9cc1d681..008e1790 100644 --- a/.github/workflows/web.yaml +++ b/.github/workflows/web.yaml @@ -11,7 +11,7 @@ jobs: - name: Test using Node.js uses: actions/setup-node@v1 with: - node-version: "17" + node-version: "18" - run: yarn install - run: yarn test diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 00000000..969045f8 --- /dev/null +++ b/.yarnrc @@ -0,0 +1,2 @@ +registry "https://registry.npmmirror.com/" +strict-ssl false \ No newline at end of file diff --git a/package.json b/package.json index 55402a4f..89a35856 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,14 @@ "@types/webpack": "^5.28.0", "@types/webpack-dev-server": "^4.7.2", "@wasm-tool/wasm-pack-plugin": "^1.6.0", + "ahooks": "^3.7.11", "antd": "^5.8.3", "craco-plugin-scoped-css": "^1.1.1", "css-loader": "^6.6.0", "eslint-plugin-react-hooks": "^4.3.0", "html-webpack-plugin": "^5.5.0", + "mobx": "^6.12.3", + "mobx-react": "^9.1.1", "node-sass": "^7.0.1", "numfmt": "2.1.0", "react": "^17.0.2", @@ -42,7 +45,7 @@ "webpack-dev-server": "^4.7.4" }, "scripts": { - "test": "jest -c jest.config.ts", + "test": "vitest run", "run-scripts": "ts-node scripts/index.ts", "start:react": "react-scripts start", "start:dev": "webpack serve --config=webpack.config.ts --hot --env=dev", @@ -102,6 +105,7 @@ "react-toastify": "^9.0.5", "reflect-metadata": "^0.1.13", "ts-jest": "^27.1.3", + "vitest": "^1.4.0", "webpack-cli": "^4.9.2" } -} \ No newline at end of file +} diff --git a/src/components/canvas/contextmenu.tsx b/src/components/canvas/contextmenu.tsx index 90c0cc3e..a13118b3 100644 --- a/src/components/canvas/contextmenu.tsx +++ b/src/components/canvas/contextmenu.tsx @@ -1,9 +1,9 @@ -import {Range, StandardBlock} from '@/core/standable' -import {SelectBlockComponent} from './select-block' -import {Cell} from './defs' -import {useState, ReactElement, MouseEvent} from 'react' -import {useInjection} from '@/core/ioc/provider' -import {ContextMenuComponent, ContextMenuItem} from '@/ui/contextmenu' +import { Range, StandardBlock } from '@/core/standable' +import { SelectBlockComponent } from './select-block' +import { Cell } from './defs' +import { useState, ReactElement, MouseEvent } from 'react' +import { useInjection } from '@/core/ioc/provider' +import { ContextMenuComponent, ContextMenuItem } from '@/ui/contextmenu' import { DeleteBlockColsBuilder, DeleteColsBuilder, @@ -16,8 +16,8 @@ import { DeleteRowsBuilder, CreateBlockBuilder, } from '@logisheets_bg' -import {TYPES} from '@/core/ioc/types' -import {Backend, SheetService} from '@/core/data' +import { TYPES } from '@/core/ioc/types' +import { Backend, SheetService } from '@/core/data' export interface ContextmenuProps { mouseevent: MouseEvent @@ -28,7 +28,7 @@ export interface ContextmenuProps { } export const ContextmenuComponent = (props: ContextmenuProps) => { - const {mouseevent, startCell, isOpen, setIsOpen, endCell} = props + const { mouseevent, startCell, isOpen, setIsOpen, endCell } = props const [blockMenuOpened, setBlockMenuOpened] = useState(false) const BACKEND_SERVICE = useInjection(TYPES.Backend) const SHEET_SERVICE = useInjection(TYPES.Sheet) @@ -56,7 +56,7 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _addCol = () => { const sheet = SHEET_SERVICE.getActiveSheet() const { - coordinate: {startCol: start}, + coordinate: { startCol: start }, } = startCell const blocks = _checkBlock() if (blocks.length !== 0) { @@ -83,7 +83,7 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _removeCol = () => { const { - coordinate: {startCol: start}, + coordinate: { startCol: start }, } = startCell const sheet = SHEET_SERVICE.getActiveSheet() const blocks = _checkBlock() @@ -111,7 +111,7 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _addRow = () => { const { - coordinate: {startRow: start}, + coordinate: { startRow: start }, } = startCell const sheet = SHEET_SERVICE.getActiveSheet() const blocks = _checkBlock() @@ -138,7 +138,7 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _removeRow = () => { const { - coordinate: {startRow: start}, + coordinate: { startRow: start }, } = startCell const sheet = SHEET_SERVICE.getActiveSheet() const blocks = _checkBlock() @@ -179,8 +179,8 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { } const _checkBlock = () => { - const {coordinate: start} = startCell - const {coordinate: end} = endCell ?? startCell + const { coordinate: start } = startCell + const { coordinate: end } = endCell ?? startCell const curr = new Range() .setStartRow(start.startRow) .setStartCol(start.startCol) diff --git a/src/components/canvas/index.tsx b/src/components/canvas/index.tsx index 1d3c4885..7e66e338 100644 --- a/src/components/canvas/index.tsx +++ b/src/components/canvas/index.tsx @@ -1,5 +1,5 @@ -import {SelectedCell} from './events' -import {Subscription, Subject} from 'rxjs' +import { SelectedCell } from './events' +import { Subscription, Subject } from 'rxjs' import styles from './canvas.module.scss' import { MouseEvent, @@ -24,19 +24,19 @@ import { StartCellEvent, SelectorChange, } from './widgets' -import {Cell} from './defs' -import {ScrollbarComponent} from '@/components/scrollbar' -import {EventType, KeyboardEventCode, on} from '@/core/events' -import {ContextmenuComponent} from './contextmenu' -import {SelectorComponent} from '@/components/selector' -import {ResizerComponent} from '@/components/resize' -import {BlurEvent, TextContainerComponent} from '@/components/textarea' -import {DndComponent} from '@/components/dnd' -import {InvalidFormulaComponent} from './invalid-formula' -import {Buttons} from '@/core' -import {CellInputBuilder} from '@logisheets_bg' -import {DialogComponent} from '@/ui/dialog' -import {useInjection} from '@/core/ioc/provider' +import { Cell } from './defs' +import { ScrollbarComponent } from '@/components/scrollbar' +import { EventType, KeyboardEventCode, on } from '@/core/events' +import { ContextmenuComponent } from './contextmenu' +import { SelectorComponent } from '@/components/selector' +import { ResizerComponent } from '@/components/resize' +import { ITextareaInstance, TextContainerComponent } from '@/components/textarea' +import { DndComponent } from '@/components/dnd' +import { InvalidFormulaComponent } from './invalid-formula' +import { Buttons } from '@/core' +import { CellInputBuilder } from '@logisheets_bg' +import { DialogComponent } from '@/ui/dialog' +import { useInjection } from '@/core/ioc/provider' import { Backend, DataService, @@ -44,7 +44,7 @@ import { RenderCell, SheetService, } from '@/core/data' -import {TYPES} from '@/core/ioc/types' +import { TYPES } from '@/core/ioc/types' export const OFFSET = 100 export interface CanvasProps { @@ -57,9 +57,10 @@ export const CanvasComponent: FC = ({ selectedCell$, }) => { const [contextmenuOpen, setContextMenuOpen] = useState(false) + const [invalidFormulaWarning, setInvalidFormulaWarning] = useState(false) const [contextMenuEl, setContextMenu] = useState() const canvasEl = useRef(null) - const BACKEND_SERVICE = useInjection(TYPES.Backend) + const textEl = useRef() const SHEET_SERVICE = useInjection(TYPES.Sheet) const DATA_SERVICE = useInjection(TYPES.Data) @@ -94,14 +95,12 @@ export const CanvasComponent: FC = ({ return newResult } - const scrollbarMng = useScrollbar({canvas: canvasEl}) + const scrollbarMng = useScrollbar({ canvas: canvasEl }) const dndMng = useDnd(canvasEl) const highlights = useHighlightCell() const resizerMng = useResizers(canvasEl) const matchMng = useMatch(canvasEl) - const focus$ = useRef(new Subject()) - useEffect(() => { if (selectedCell.source != 'editbar') return @@ -113,6 +112,46 @@ export const CanvasComponent: FC = ({ textMng.startCellChange(e) }, [selectedCell]) + useEffect(() => { + const sub = on(window, EventType.MOUSE_MOVE).subscribe((mme) => { + mme.preventDefault() + if (!startCellMng.isMouseDown.current) return + const startCell = matchMng.match( + mme.clientX, + mme.clientY, + DATA_SERVICE.cachedViewRange + ) + if (!startCell) return + const isResize = resizerMng.mousemove(mme) + if (isResize) return + if ( + startCellMng.startCell.current?.equals(startCell) === false + ) { + const isDnd = dndMng.onMouseMove( + mme, + startCell, + selectorMng.endCell ?? startCell + ) + if (isDnd) return + } + selectorMng.onMouseMove(startCell) + }) + return () => { + sub.unsubscribe() + } + }, []) + + useEffect(() => { + const sub = on(window, EventType.MOUSE_UP).subscribe(() => { + startCellMng.isMouseDown.current = false + dndMng.onMouseUp() + resizerMng.mouseup() + }) + return () => { + sub.unsubscribe() + } + }, []) + const rendered = () => { resizerMng.init() } @@ -127,27 +166,27 @@ export const CanvasComponent: FC = ({ textMng.startCellChange(e) if (e === undefined || e.same) return if (e?.cell?.type !== 'Cell') return - const {startRow: row, startCol: col} = e.cell.coordinate - selectedCell$({row, col, source: 'none'}) + const { startRow: row, startCol: col } = e.cell.coordinate + selectedCell$({ row, col, source: 'none' }) } - const startCellMng = useStartCell({startCellChange}) + const startCellMng = useStartCell({ startCellChange }) const selectorChange: SelectorChange = (selector) => { if (!selector) { dndMng.clean() return } - const {startCell: start, endCell: end} = selector - dndMng.selectorChange({start, end}) + const { startCell: start, endCell: end } = selector + dndMng.selectorChange({ start, end }) } - const selectorMng = useSelector({canvas: canvasEl, selectorChange}) + const selectorMng = useSelector({ canvas: canvasEl, selectorChange }) const onEdit = (editing: boolean, text?: string) => { if (!editing) return if (text === undefined) return highlights.init(text) } - const textMng = useText({canvas: canvasEl, onEdit}) + const textMng = useText({ canvas: canvasEl, onEdit }) const setScrollTop = (scrollTop: number, type: 'x' | 'y') => { scrollbarMng.setScrollTop(scrollTop, type) @@ -170,59 +209,24 @@ export const CanvasComponent: FC = ({ const onMouseDown = async (e: MouseEvent) => { e.stopPropagation() e.preventDefault() - const mousedown = async () => { - canvasEl.current?.focus() - const isBlur = await textMng.blur() - if (!isBlur) { - focus$.current.next() - return - } - if (e.buttons === Buttons.RIGHT) return - const matchCell = matchMng.match( - e.clientX, - e.clientY, - DATA_SERVICE.cachedViewRange - ) - if (!matchCell) return - const isResize = resizerMng.mousedown(e.nativeEvent) - if (isResize) return - const isDnd = dndMng.onMouseDown(e.nativeEvent) - if (isDnd) return - startCellMng.mousedown(e, matchCell) + const isBlur = await onBlur() + if (!isBlur) { + textEl.current?.focus() + return } - mousedown() - const sub = new Subscription() - sub.add( - on(window, EventType.MOUSE_UP).subscribe(() => { - dndMng.onMouseUp() - resizerMng.mouseup() - sub.unsubscribe() - }) - ) - sub.add( - on(window, EventType.MOUSE_MOVE).subscribe((mme) => { - mme.preventDefault() - const startCell = matchMng.match( - mme.clientX, - mme.clientY, - DATA_SERVICE.cachedViewRange - ) - if (!startCell) return - const isResize = resizerMng.mousemove(mme) - if (isResize) return - if ( - startCellMng.startCell.current?.equals(startCell) === false - ) { - const isDnd = dndMng.onMouseMove( - mme, - startCell, - selectorMng.endCell ?? startCell - ) - if (isDnd) return - } - selectorMng.onMouseMove(startCell) - }) + canvasEl.current?.focus() + if (e.buttons === Buttons.RIGHT) return + const matchCell = matchMng.match( + e.clientX, + e.clientY, + DATA_SERVICE.cachedViewRange ) + if (!matchCell) return + const isResize = resizerMng.mousedown(e.nativeEvent) + if (isResize) return + const isDnd = dndMng.onMouseDown(e.nativeEvent) + if (isDnd) return + startCellMng.mousedown(e, matchCell) } const onKeyDown = async (e: KeyboardEvent) => { @@ -271,20 +275,18 @@ export const CanvasComponent: FC = ({ } } - const blur = (e: BlurEvent) => { - const oldText = textMng.context?.text ?? '' - textMng.blur() + const onBlur = async () => { + const isBlur = await textMng.blur() + if (!isBlur) { + setInvalidFormulaWarning(true) + return false + } highlights.blur() - if (e.bindingData === undefined) return - const newText = textMng.currText.current.trim() - if (oldText === newText) return - const payload = new CellInputBuilder() - .row(e.bindingData.coordinate.startRow) - .col(e.bindingData.coordinate.startCol) - .sheetIdx(SHEET_SERVICE.getActiveSheet()) - .input(newText) - .build() - BACKEND_SERVICE.sendTransaction([payload]) + return true + } + const onCloseInvalidFormulaWarning = () => { + setInvalidFormulaWarning(false) + textEl.current?.focus() } const onContextMenu = (e: MouseEvent) => { @@ -343,15 +345,14 @@ export const CanvasComponent: FC = ({ {...scrollbarMng.yScrollbarAttr} setScrollTop={(e) => setScrollTop(e, 'y')} > - {textMng.context && textMng.editing ? ( + {textMng.context && textMng.editing && ( - ) : null} + /> + )} {dndMng.range ? ( = ({ draggingY={dndMng.dragging?.startRow} > ) : null} - textMng.setValidFormulaOpen(false)} - > - } - close$={() => textMng.setValidFormulaOpen(false)} - isOpen={textMng.validFormulaOpen} - > + + + {highlights.highlightCells.map((cell, i) => { const cellStyle = cell.style return ( @@ -389,8 +384,8 @@ export const CanvasComponent: FC = ({ ) })} {resizerMng.resizers.map((resizer, i) => { - const {startCol: x, startRow: y, width, height} = resizer.range - const {isRow} = resizer + const { startCol: x, startRow: y, width, height } = resizer.range + const { isRow } = resizer const rect = ( canvasEl.current as HTMLCanvasElement ).getBoundingClientRect() diff --git a/src/components/canvas/widgets/start-cell.ts b/src/components/canvas/widgets/start-cell.ts index aaad298d..f3cf49df 100644 --- a/src/components/canvas/widgets/start-cell.ts +++ b/src/components/canvas/widgets/start-cell.ts @@ -28,6 +28,7 @@ interface StartCellProps { export const useStartCell = ({startCellChange}: StartCellProps) => { const DATA_SERVICE = useInjection(TYPES.Data) const startCell = useRef() + const isMouseDown = useRef(false) const scroll = () => { const oldStartCell = startCell.current @@ -100,6 +101,7 @@ export const useStartCell = ({startCellChange}: StartCellProps) => { .setEndCol(selector.x + selector.borderRightWidth) if (range.cover(matchCell.position)) return } + isMouseDown.current = true const event = new StartCellEvent( matchCell, buttons === Buttons.LEFT ? 'mousedown' : 'contextmenu' @@ -111,6 +113,7 @@ export const useStartCell = ({startCellChange}: StartCellProps) => { } return { startCell, + isMouseDown, scroll, canvasChange, mousedown, diff --git a/src/components/canvas/widgets/text.ts b/src/components/canvas/widgets/text.ts index 06f11ffa..869574d6 100644 --- a/src/components/canvas/widgets/text.ts +++ b/src/components/canvas/widgets/text.ts @@ -8,9 +8,10 @@ import initFc, { formula_check, } from '../../../../crates/wasms/fc/pkg/logisheets_wasm_fc' import {isFormula} from '@/core/snippet' -import {SheetService} from '@/core/data' +import {Backend, SheetService} from '@/core/data' import {useInjection} from '@/core/ioc/provider' import {TYPES} from '@/core/ioc/types' +import { CellInputBuilder } from '@logisheets_bg' interface TextProps { readonly canvas: RefObject @@ -19,28 +20,28 @@ interface TextProps { export const useText = ({canvas, onEdit}: TextProps) => { const SHEET_SERVICE = useInjection(TYPES.Sheet) + const BACKEND_SERVICE = useInjection(TYPES.Backend) const [editing, setEditing] = useState(false) const [context, setContext] = useState>() - const [validFormulaOpen, setValidFormulaOpen] = useState(false) const currText = useRef('') const _lastMouseDownTime = useRef(0) const blur = async () => { - const check = await checkFormula() - if (!check) return false + if (!editing) return true + const newText = currText.current.trim() + const checked = await checkFormula(newText) + if (!checked || !context?.bindingData) return false _setEditing(false) + const payload = new CellInputBuilder() + .row(context.bindingData.coordinate.startRow) + .col(context.bindingData.coordinate.startCol) + .sheetIdx(SHEET_SERVICE.getActiveSheet()) + .input(newText) + .build() + BACKEND_SERVICE.sendTransaction([payload]) return true } - const checkFormula = async () => { - if (!editing) return true - const formula = currText.current - if (!isFormula(formula)) return true - await initFc() - const checked = formula_check(formula) - setValidFormulaOpen(!checked) - return checked - } const keydown = (e: KeyboardEvent, startCell?: Cell) => { const standardEvent = new StandardKeyboardEvent(e) @@ -87,6 +88,7 @@ export const useText = ({canvas, onEdit}: TextProps) => { context.clientY = clientY ?? -1 context.cellHeight = height context.cellWidth = width + context.bindingData = startCell context.textareaOffsetX = (event as globalThis.MouseEvent).clientX - clientX context.textareaOffsetY = @@ -109,11 +111,15 @@ export const useText = ({canvas, onEdit}: TextProps) => { editing, context, currText, - setValidFormulaOpen, - validFormulaOpen, - checkFormula, blur, keydown, startCellChange, } } + + +const checkFormula = async (formula: string) => { + if (!isFormula(formula)) return true + await initFc() + return formula_check(formula) +} \ No newline at end of file diff --git a/src/components/root/index.tsx b/src/components/root/index.tsx index 47e27507..74a27111 100644 --- a/src/components/root/index.tsx +++ b/src/components/root/index.tsx @@ -1,8 +1,8 @@ -import {RootContainer} from './container' +import { RootContainer } from './container' import styles from './root.module.scss' -import {WsCommponent} from './ws' -import {ErrorBoundary} from '@/ui/error' -import {NotificatonComponent} from '@/ui/notification' +import { WsCommponent } from './ws' +import { ErrorBoundary } from '@/ui/error' +import { NotificatonComponent } from '@/ui/notification' export const SpreadsheetRoot = () => { return ( diff --git a/src/components/suggest/index.tsx b/src/components/suggest/index.tsx index 3a2ec388..b5fd4980 100644 --- a/src/components/suggest/index.tsx +++ b/src/components/suggest/index.tsx @@ -1,19 +1,16 @@ -import {Styles} from './styles' -import {FC, MouseEvent, useState} from 'react' -import {Candidate} from './item' -import {SuggestDetailsComponent} from './details' +import { Styles } from './styles' +import { FC, MouseEvent, useState } from 'react' +import { Candidate } from './item' +import { SuggestDetailsComponent } from './details' import styles from './suggest.module.scss' export * from './item' export * from './styles' export interface SuggestProps { readonly show$: boolean - // 关闭suggest readonly close$: () => void - // 选择某个candidate - readonly select$: (candidate: Candidate) => void + readonly select$: (candidate: number) => void readonly sugggestStyles: Styles - readonly acitveCandidate?: Candidate - // 所有candidates + readonly acitveCandidate?: number readonly candidates?: Candidate[] } export const SuggestComponent: FC = ({ @@ -26,10 +23,11 @@ export const SuggestComponent: FC = ({ }) => { const [showDetails, setShowDetails] = useState(false) - const mouseDown = (e: MouseEvent, candidate: Candidate) => { + const mouseDown = (e: MouseEvent, candidate: number) => { + const _active = candidates[candidate] e.preventDefault() e.stopPropagation() - if (candidate.textOnly) return + if (_active.textOnly) return select$(candidate) close$() } @@ -49,16 +47,15 @@ export const SuggestComponent: FC = ({ className={`${styles.suggest} ${candidate.disable ? styles.disabled : ''} ${candidate.textOnly ? styles['text-only'] : ''} - ${candidate === acitveCandidate ? styles['active'] : ''}`} - onMouseDown={(e) => mouseDown(e, candidate)} + ${i === acitveCandidate ? styles['active'] : ''}`} + onMouseDown={(e) => mouseDown(e, i)} > {candidate.spans.map((s, j) => { return ( {s.text} diff --git a/src/components/textarea/component.tsx b/src/components/textarea/component.tsx new file mode 100644 index 00000000..e58dd4ca --- /dev/null +++ b/src/components/textarea/component.tsx @@ -0,0 +1,272 @@ +import { EventType, StandardKeyboardEvent, KeyboardEventCode } from '@/core/events' +import { observer } from 'mobx-react' +import { forwardRef, useImperativeHandle } from 'react' +import { Context, ITextareaInstance, Text } from './defs' +import { CursorComponent } from './cursor' +import { + SyntheticEvent, + useContext, + useEffect, + useRef, +} from 'react' +import styles from './textarea.module.scss' +import { TextareaContext, TextareaStore } from './store' +import { SuggestComponent } from '../suggest' + +export interface TextContainerProps { + context: Context + blur: () => void + type: (e: string) => void +} + +export const TextContainerComponent = forwardRef((props: TextContainerProps, ref) => { + const store = useRef(new TextareaStore(props.context)) + const internal = useRef() + + useImperativeHandle(ref, () => { + return internal.current + }) + return + + +}) + +const InternalComponent = observer(forwardRef((props: TextContainerProps, ref) => { + const { context, blur, type } = props + const { cursor, selection } = useContext(TextareaContext) + + useImperativeHandle(ref, () => { + return { + focus: onFocus, + } as ITextareaInstance + }) + + const textareaEl = useRef(null) + const textEl = useRef(null) + const selectionEl = useRef(null) + const store = useContext(TextareaContext) + + useEffect(() => { + selection.init(selectionEl.current!) + }, []) + + useEffect(() => { + const sub = store.cursor.cursor$.subscribe(() => { + type(store.textManager.getPlainText()) + store.suggest.onType() + store.selection.clear() + }) + return () => { + sub.unsubscribe() + } + }, []) + + const onBlur = (event: SyntheticEvent) => { + // TODO: implement brace if has formula + // store.cursor.blur() + // blur() + } + + const onType = (value: string, event: SyntheticEvent) => { + if (textareaEl.current) { + textareaEl.current.value = '' + } + const newTexts = store.textManager.add(value) + store.cursor.type(newTexts, []) + } + + const onFocus = () => { + setTimeout(() => { + textareaEl.current?.focus() + if (textEl.current) + store.textManager.drawText(textEl.current) + store.cursor.focus() + }) + } + + const handleEvent = async (event: SyntheticEvent) => { + const type = event.type + switch (type) { + case EventType.FOCUS: + onFocus() + break + case EventType.BLUR: + onBlur(event) + break + case EventType.KEY_DOWN: { + const e = new StandardKeyboardEvent(event.nativeEvent as any) + const disableInput = () => { + e.e.returnValue = false + } + if (store.isComposing) { + disableInput() + return + } + switch (e.keyCodeId) { + case KeyboardEventCode.DELETE: + case KeyboardEventCode.BACKSPACE: { + disableInput() + let removed: readonly Text[] = [] + if (store.selection.selection) { + const { startLine, endLine, startColumn, endColumn } = store.selection.selection + removed = store.textManager.removeInTwoDimensional({ + startLine, + endLine, + startColumn, + endColumn: endColumn - 1 < 0 ? 0 : endColumn - 1 + }) + } else { + const currPosition = store.cursor.cursorPosition + if (currPosition === 0) return + removed = store.textManager.remove(currPosition - 1, currPosition - 1) + } + store.cursor.type([], removed) + break + } + case KeyboardEventCode.ENTER: { + disableInput() + if (store.suggest.showSuggest) { + store.suggest.onSuggest() + return + } + else if (e.isAlt()) { + onType(store.context.eof, event) + } else + return onBlur(event) + break + } + case KeyboardEventCode.ARROW_LEFT: { + disableInput() + const line = store.cursor.lineNumber + const column = store.cursor.column + store.cursor.updateTwoDimensionalPosition(line, column - 1) + break + } + case KeyboardEventCode.ARROW_RIGHT: { + disableInput() + const line = store.cursor.lineNumber + const column = store.cursor.column + store.cursor.updateTwoDimensionalPosition(line, column + 1) + break + } + case KeyboardEventCode.ARROW_UP: { + disableInput() + if (store.suggest.showSuggest) { + store.suggest.activeCandidate -= 1 + return + } + const line = store.cursor.lineNumber + const column = store.cursor.column + store.cursor.updateTwoDimensionalPosition(line - 1, column) + break + } + case KeyboardEventCode.ARROW_DOWN: { + disableInput() + if (store.suggest.showSuggest) { + store.suggest.activeCandidate += 1 + return + } + const line = store.cursor.lineNumber + const column = store.cursor.column + store.cursor.updateTwoDimensionalPosition(line + 1, column) + break + } + default: + } + break + } + case EventType.INPUT: { + if (store.isComposing) { + return + } + const currValue: string = (event.nativeEvent.target as any)?.value ?? '' + let inputValue = currValue.at(-1) || '' + onType(inputValue, event) + break + } + case EventType.COMPOSITION_START: + store.cursor.compositionStart = store.cursor.cursorPosition + store.setComposing(true) + break + case EventType.COMPOSITION_END: { + const currValue: string = (event.nativeEvent.target as any)?.value ?? '' + onType(currValue, event) + store.cursor.compositionStart = -1 + store.setComposing(false) + break + } + case EventType.MOUSE_DOWN: { + event.preventDefault() + event.stopPropagation() + store.cursor.mousedown(event.nativeEvent as any) + store.isMousedown = true + break + } + case EventType.MOUSE_MOVE: { + event.preventDefault() + if (store.isMousedown) + store.selection.mousemove(event.nativeEvent as any) + break + } + case EventType.MOUSE_UP: { + event.preventDefault() + store.isMousedown = false + break + } + default: + } + } + + return ( +
+ + + + {cursor.showCursor && } + store.suggest.showSuggest = false} + select$={store.suggest.onSuggest} + sugggestStyles={{ x: 0, y: context.cellHeight }} + acitveCandidate={store.suggest.activeCandidate} + candidates={store.suggest.candidates} + /> +
+ ) +})) \ No newline at end of file diff --git a/src/components/textarea/cursor/cursor-info.ts b/src/components/textarea/cursor/cursor-info.ts index fc76d3db..8298d91b 100644 --- a/src/components/textarea/cursor/cursor-info.ts +++ b/src/components/textarea/cursor/cursor-info.ts @@ -1,27 +1,37 @@ -import {equal} from '@/core' +import { equal } from "@/core" export class BaseInfo { - public x = 0 - public y = 0 - public lineNumber = 0 - public column = 0 + x = 0 + y = 0 + lineNumber = 0 + column = 0 + setX(x: number) { + let _x = Math.max(x, 0) + this.x = _x + return this + } + setY(y: number) { + let _y = Math.max(y, 0) + this.y = _y + return this + } + setLineNumber(lineNumber: number) { + let _l = Math.max(lineNumber, 0) + this.lineNumber = _l + return this + } + setColumn(column: number) { + let _c = Math.max(column, 0) + this.column = _c + return this + } equal(baseInfo: BaseInfo) { return equal(baseInfo, this) } biggerThan(baseInfo: BaseInfo): boolean { - if (this.y > baseInfo.y) return true - if (this.y === baseInfo.y && this.x > baseInfo.x) return true - return false - } -} - -export class CursorInfo extends BaseInfo { - public height = 0 - updateFromBaseInfo(baseInfo: BaseInfo): void { - this.x = baseInfo.x - this.y = baseInfo.y - this.lineNumber = baseInfo.lineNumber - this.column = baseInfo.column + if (this.lineNumber > baseInfo.lineNumber) return true + if (this.lineNumber < baseInfo.lineNumber) return false + return this.column >= baseInfo.column } } diff --git a/src/components/textarea/defs/context.ts b/src/components/textarea/defs/context.ts index 88f4f4bb..5e7fa5ba 100644 --- a/src/components/textarea/defs/context.ts +++ b/src/components/textarea/defs/context.ts @@ -1,5 +1,4 @@ -import {Position} from '../input' -export class Context { +export class Context { public text = '' public eof = '\n' public cellWidth = 0 @@ -27,29 +26,8 @@ export class Context { ): readonly [offsetX: number, offsetY: number] { return [x - this.clientX, y - this.clientY] } - - getTexts( - startPosition?: Position, - endPosition?: Position - ): readonly string[] { - const texts = this.text.split(this.eof) - const start = startPosition ?? new Position() - const endLine = texts.length - 1 - const endCol = texts[endLine].length - 1 - let end = endPosition - if (!end) { - end = new Position() - end.lineNumber = endLine - end.column = endCol - } - if (start.lineNumber === end.lineNumber) - return [texts[start.lineNumber].slice(start.column, end.column + 1)] - const r: string[] = [] - for (let i = start.lineNumber; i <= end.lineNumber; i += 1) - if (i === start.lineNumber) r.push(texts[i].slice(start.column)) - else if (i === end.lineNumber) - r.push(texts[i].slice(0, end.column + 1)) - else r.push(texts[i]) - return r - } } + +export interface ITextareaInstance { + focus: () => void +} \ No newline at end of file diff --git a/src/components/textarea/events/blur-event.ts b/src/components/textarea/events/blur-event.ts deleted file mode 100644 index 2bc752c6..00000000 --- a/src/components/textarea/events/blur-event.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class BlurEvent { - constructor(public readonly bindingData?: T) {} -} diff --git a/src/components/textarea/events/cursor-event.ts b/src/components/textarea/events/cursor-event.ts deleted file mode 100644 index ac533e03..00000000 --- a/src/components/textarea/events/cursor-event.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class CursorEvent { - public offsetX = 0 - public offsetY = 0 - public clientX = 0 - public clientY = 0 - public show = false - public lineNumber = 0 - public columnNumber = 0 -} diff --git a/src/components/textarea/events/index.ts b/src/components/textarea/events/index.ts deleted file mode 100644 index 535ced52..00000000 --- a/src/components/textarea/events/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './blur-event' -export * from './cursor-event' diff --git a/src/components/textarea/index.ts b/src/components/textarea/index.ts new file mode 100644 index 00000000..fd27344d --- /dev/null +++ b/src/components/textarea/index.ts @@ -0,0 +1,2 @@ +export * from './defs' +export * from './component' \ No newline at end of file diff --git a/src/components/textarea/index.tsx b/src/components/textarea/index.tsx deleted file mode 100644 index 210818b3..00000000 --- a/src/components/textarea/index.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import {on, EventType, StandardKeyboardEvent} from '@/core/events' -import { - useSelection, - useCursor, - InputManager, - TextManager, - useSuggest, -} from './managers' -import {Context} from './defs' -import {BlurEvent} from './events' -import {CursorComponent} from './cursor' -import { - ClipboardEvent, - CompositionEvent, - FocusEvent, - KeyboardEvent, - MouseEvent, - useEffect, - useRef, -} from 'react' -import {SuggestComponent} from '@/components/suggest' -import {Subscription, Observable} from 'rxjs' -import styles from './textarea.module.scss' - -export * from './events' -export * from './managers' -export * from './defs' - -export interface TextContainerProps { - context: Context - blur?: (e: BlurEvent) => void - type?: (e: string) => void - checkFormula: (text?: string) => Promise - focus$: Observable -} - -export const TextContainerComponent = ({ - context, - blur, - type, - checkFormula, - focus$, -}: TextContainerProps) => { - const textMng = useRef(new TextManager(context)) - const cursorMng = useCursor(textMng.current, context) - const selectionMng = useSelection(cursorMng, context, textMng.current) - const inputMng = useRef( - new InputManager(textMng.current, selectionMng, context, cursorMng) - ) - const suggestMng = useSuggest(textMng.current, cursorMng) - const isMouseDown = useRef(false) - - const textEl = useRef(null) - const selectionEl = useRef(null) - const textareaEl = useRef(null) - - useEffect(() => { - const textElement = textEl.current - const selectionElement = selectionEl.current - const textareaElement = textareaEl.current - if (textElement) textMng.current.drawText(textElement) - if (selectionElement) selectionMng.init(selectionElement) - if (textareaElement) inputMng.current.init(textareaElement) - }, [context]) - useEffect(() => { - const sub = new Subscription() - sub.add( - on(window, EventType.MOUSE_MOVE).subscribe((mme) => { - if (!isMouseDown.current) return - mme.preventDefault() - selectionMng.mousemove(mme) - }) - ) - sub.add( - on(window, EventType.MOUSE_UP).subscribe((mue) => { - if (!isMouseDown.current) return - mue.preventDefault() - isMouseDown.current = false - sub.unsubscribe() - }) - ) - sub.add( - textMng.current.textChanged().subscribe((t) => { - type?.(t) - suggestMng.onType() - }) - ) - sub.add( - focus$.subscribe(() => { - inputMng.current.setFocus() - }) - ) - return () => { - // 目前不知道为什么点击到另一个单元格不会触发blur事件,只好这里先做这个blur这个事情 - _onBlur().then((shouldClose) => { - if (!shouldClose) return - sub.unsubscribe() - }) - } - }, []) - - const onHostMouseDown = (mde: MouseEvent) => { - mde.stopPropagation() - mde.preventDefault() - if (!inputMng.current.hasFocus()) inputMng.current.setFocus() - cursorMng.mousedown(mde) - selectionMng.mousedown(mde) - isMouseDown.current = true - } - const onHostKeyDown = (e: KeyboardEvent) => { - e.stopPropagation() - const event = new StandardKeyboardEvent(e.nativeEvent) - const finish = suggestMng.onKeydown(event) - if (finish) return - inputMng.current.textareaInput?.onKeydown(e.nativeEvent) - } - const onHostKeyup = (e: KeyboardEvent) => { - inputMng.current.textareaInput?.onKeyup(e.nativeEvent) - } - const onHostCompositionStart = (e: CompositionEvent) => { - inputMng.current.textareaInput?.onCompositionnStart(e.nativeEvent) - } - const onHostCompositionUpdate = (e: CompositionEvent) => { - inputMng.current.textareaInput?.onCompositionUpdate(e.nativeEvent) - } - const onHostCompositionEnd = (e: CompositionEvent) => { - inputMng.current.textareaInput?.onCompositionEnd(e.nativeEvent) - } - const onHostInput = () => { - inputMng.current.textareaInput?.onInput() - } - const onHostCut = (e: ClipboardEvent) => { - inputMng.current.textareaInput?.onCut(e.nativeEvent) - } - const onHostCopy = (e: ClipboardEvent) => { - inputMng.current.textareaInput?.onCopy(e.nativeEvent) - } - const onHostPaste = (e: ClipboardEvent) => { - inputMng.current.textareaInput?.onPaste(e.nativeEvent) - } - const onHostFocus = (e: FocusEvent) => { - inputMng.current.textareaInput?.onFocus(e.nativeEvent) - } - const onHostBlur = () => { - inputMng.current.textareaInput?.onBlur() - } - - const _onBlur = async () => { - const check = await checkBlur() - if (!check) { - inputMng.current.setFocus() - return false - } - const event = new BlurEvent(context.bindingData) - blur?.(event) - return true - } - - const checkBlur = async () => { - const formula = textMng.current.getPlainText() - // 补全括号 - - // 检查是否为合法公式(wasm),再发往服务端 - const valid = await checkFormula(formula) - if (!valid) return false - - return true - } - return ( -
- - - - - suggestMng.setShowSuggest(false)} - select$={suggestMng.onSuggest} - sugggestStyles={{x: 0, y: context.cellHeight}} - acitveCandidate={suggestMng.activeCandidate$} - candidates={suggestMng.candidates$} - > -
- ) -} diff --git a/src/components/textarea/input/index.ts b/src/components/textarea/input/index.ts deleted file mode 100644 index dbc3b857..00000000 --- a/src/components/textarea/input/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './selection' -export * from './textarea-input' -export * from './textarea-input-events' -export * from './textarea-input-host' -export * from './textarea-state' -export * from './textarea-wrapper' -export * from './type_data' -export * from './position' diff --git a/src/components/textarea/input/position.ts b/src/components/textarea/input/position.ts deleted file mode 100644 index f5a70946..00000000 --- a/src/components/textarea/input/position.ts +++ /dev/null @@ -1,30 +0,0 @@ -export function positionEquals(a?: Position, b?: Position): boolean { - if (!a && !b) return true - return !!a && !!b && a.lineNumber === b.lineNumber && a.column === b.column -} -export class Position { - public lineNumber = 0 - public column = 0 - public with( - newLineNumber = this.lineNumber, - newColumn = this.column - ): Position { - if (newLineNumber === this.lineNumber && newColumn === this.column) - return this - const newPosition = new Position() - newPosition.lineNumber = newLineNumber - newPosition.column = newColumn - return newPosition - } - - public delta(deltaLineNumber = 0, deltaColumn = 0): Position { - return this.with( - this.lineNumber + deltaLineNumber, - this.column + deltaColumn - ) - } - - public equals(other: Position): boolean { - return positionEquals(this, other) - } -} diff --git a/src/components/textarea/input/selection.ts b/src/components/textarea/input/selection.ts deleted file mode 100644 index 1b376be4..00000000 --- a/src/components/textarea/input/selection.ts +++ /dev/null @@ -1,96 +0,0 @@ -export function selectionEquals(a: Selection, b: Selection) { - return ( - a.positionColumn === b.positionColumn && - a.positionLineNumber === b.positionLineNumber && - a.startColumn === b.startColumn && - a.startLineNumber === b.startLineNumber - ) -} -export const enum SelectionDirection { - LTR, - RTL, -} -export class Selection { - set selectionStartLineNumber(selectionStartLineNumber: number) { - this.#selectionStartLineNumber = selectionStartLineNumber - this.startLineNumber = selectionStartLineNumber - } - get selectionStartLineNumber() { - return this.#selectionStartLineNumber - } - - set selectionStartColumn(selectionStartColumn: number) { - this.#selectionStartColumn = selectionStartColumn - this.startColumn = selectionStartColumn - } - get selectionStartColumn() { - return this.#selectionStartColumn - } - - set positionLineNumber(positionLineNumber: number) { - this.#positionLineNumber = positionLineNumber - this.endLineNumber = positionLineNumber - } - get positionLineNumber() { - return this.#positionLineNumber - } - - set positionColumn(positionColumn: number) { - this.#positionColumn = positionColumn - this.endColumn = positionColumn - } - get positionColumn() { - return this.#positionColumn - } - - startLineNumber = 0 - startColumn = 0 - endLineNumber = 0 - endColumn = 0 - #selectionStartLineNumber = 0 - #selectionStartColumn = 0 - #positionLineNumber = 0 - #positionColumn = 0 - public equals(other: Selection) { - return selectionEquals(this, other) - } - - public getDirection() { - return this.#selectionStartLineNumber === this.startLineNumber && - this.#selectionStartColumn === this.startColumn - ? SelectionDirection.LTR - : SelectionDirection.RTL - } - - public setEndPosition(endLineNumber: number, endColumn: number) { - const selection = new Selection() - if (this.getDirection() === SelectionDirection.LTR) { - selection.selectionStartLineNumber = this.startLineNumber - selection.selectionStartColumn = this.startColumn - selection.positionLineNumber = endLineNumber - selection.positionColumn = endColumn - } else { - selection.selectionStartLineNumber = endLineNumber - selection.selectionStartColumn = endColumn - selection.positionLineNumber = this.startLineNumber - selection.positionColumn = this.startColumn - } - return selection - } - - public setStartPosition(startLineNumber: number, startColumn: number) { - const selection = new Selection() - if (this.getDirection() === SelectionDirection.LTR) { - selection.selectionStartLineNumber = startLineNumber - selection.selectionStartColumn = startColumn - selection.positionLineNumber = this.endLineNumber - selection.positionColumn = this.endColumn - } else { - selection.selectionStartLineNumber = this.endLineNumber - selection.selectionStartColumn = this.endColumn - selection.positionLineNumber = startLineNumber - selection.positionColumn = startColumn - } - return selection - } -} diff --git a/src/components/textarea/input/textarea-input-events.ts b/src/components/textarea/input/textarea-input-events.ts deleted file mode 100644 index d2770c20..00000000 --- a/src/components/textarea/input/textarea-input-events.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - CompositionStartEvent, - CompositionData, - ClipboardStoredMetaData, - StandardKeyboardEvent, -} from '@/core/events' -import {Subject} from 'rxjs' -import {TypeData} from './type_data' -import {Selection} from './selection' - -export class TextAreaInputEvents { - onKeyDown$ = new Subject() - onKeyUp$ = new Subject() - onCompositionStart$ = new Subject() - onCompositionUpdate$ = new Subject() - onCompositionEnd$ = new Subject() - onFocus$ = new Subject() - onBlur$ = new Subject() - onCut$ = new Subject() - onPaste$ = new Subject() - onType$ = new Subject() - onSelectionChange$ = new Subject() -} diff --git a/src/components/textarea/input/textarea-input-host.ts b/src/components/textarea/input/textarea-input-host.ts deleted file mode 100644 index 1b08dde6..00000000 --- a/src/components/textarea/input/textarea-input-host.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Position} from './position' -import {ClipboardStoredMetaData} from '@/core/events' -import {TextAreaState} from './textarea-state' -export class ClipboardDataToCopy { - constructor(public data: ClipboardStoredMetaData) {} - public text = '' -} - -export interface TextAreaInputHost { - getDataToCopy(start: Position, end: Position): ClipboardDataToCopy - getScreenReaderContent(currentState: TextAreaState): TextAreaState - deduceModelPosition( - viewAnchorPosition: Position, - deltaOffset: number, - lineFeedCnt: number - ): Position -} diff --git a/src/components/textarea/input/textarea-input.ts b/src/components/textarea/input/textarea-input.ts deleted file mode 100644 index 7aa8c106..00000000 --- a/src/components/textarea/input/textarea-input.ts +++ /dev/null @@ -1,313 +0,0 @@ -import {TextAreaWrapper} from './textarea-wrapper' -import {TextAreaInputHost} from './textarea-input-host' -import {Subscription} from 'rxjs' -import { - EventType, - on, - StandardKeyboardEvent, - canUseTextData, - getClipboardData, - MIMES, - setDataToCopy, - KeyboardEventCode, - CompositionStartEvent, - ClipboardMetaData, - ClipboardStoredMetaData, -} from '@/core/events' -import {TextAreaState} from './textarea-state' -import {isChrome, isFirefox, isMac} from '@/core/platform' -import {TypeData} from './type_data' -import {Selection} from './selection' -import {Position} from './position' -import {TextAreaInputEvents} from './textarea-input-events' -import {isHighSurrogate} from '@/core/strings' -import {shallowCopy} from '@/core' -const enum ReadFromTextArea { - TYPE, - PASTE, -} -export class TextAreaInput extends TextAreaInputEvents { - constructor( - public readonly host: TextAreaInputHost, - public readonly textarea: HTMLTextAreaElement - ) { - super() - this._textareaWrapper = new TextAreaWrapper(this.textarea) - this.writeScreenReaderContent() - } - destroy(): void { - this._subs.unsubscribe() - this._selectionChangeListener?.unsubscribe() - } - - hasFocus(): boolean { - return this._hasFocus - } - - focusTextArea(): void { - this.#setHasFocus(true) - // this.refreshFocusState() - } - - writeScreenReaderContent(): void { - if (this._isDoingComposition) return - this._setAndWriteTextAreaState( - this.host.getScreenReaderContent(this._textAreaState) - ) - } - onKeydown(e: KeyboardEvent) { - if ( - e.isComposing || - (this._isDoingComposition && e.code === KeyboardEventCode.BACKSPACE) - ) - e.stopPropagation() - if (e.code === KeyboardEventCode.ESCAPE) e.preventDefault() - this._lastKeydown = new StandardKeyboardEvent(e) - this.onKeyDown$.next(this._lastKeydown) - } - onKeyup(e: KeyboardEvent) { - this.onKeyUp$.next(e) - } - onCompositionnStart(e: CompositionEvent) { - if (this._isDoingComposition) return - this._isDoingComposition = true - if ( - isMac() && - this._textAreaState.selectionStart === - this._textAreaState.selectionEnd && - this._textAreaState.selectionStart > 0 && - this._textAreaState.value.substr( - this._textAreaState.selectionStart - 1, - 1 - ) === e.data - ) { - const isArrowKey = - this._lastKeydown && - this._lastKeydown.isComposing && - (this._lastKeydown.keyCodeId === - KeyboardEventCode.ARROW_RIGHT || - this._lastKeydown.keyCodeId === - KeyboardEventCode.ARROW_LEFT) - if (isArrowKey || isFirefox()) { - const newState = new TextAreaState() - shallowCopy(this._textAreaState, newState) - newState.selectionStart = this._textAreaState.selectionStart - 1 - if (this._textAreaState.selectionStartPosition) { - const position = new Position() - shallowCopy( - this._textAreaState.selectionStartPosition, - position - ) - position.column = - this._textAreaState.selectionStartPosition.column - 1 - newState.selectionStartPosition = position - } - this._textAreaState = newState - const compositionStartEvent = new CompositionStartEvent() - compositionStartEvent.revealDeltaColumns = -1 - this.onCompositionStart$.next(compositionStartEvent) - return - } - } - this._setAndWriteTextAreaState(TextAreaState.EMPTY) - this.onCompositionStart$.next(new CompositionStartEvent()) - } - onCompositionUpdate(e: CompositionEvent) { - const [newState, typeInput] = this.#deduceComposition(e.data || '') - this._textAreaState = newState - this.onType$.next(typeInput) - this.onCompositionUpdate$.next(e) - } - onCompositionEnd(e: CompositionEvent) { - if (!this._isDoingComposition) return - this._isDoingComposition = false - const [newState, typeInput] = this.#deduceComposition(e.data || '') - this._textAreaState = newState - this.onType$.next(typeInput) - if (isChrome() || isFirefox()) - this._textAreaState = TextAreaState.readFromTextArea( - this._textareaWrapper - ) - this.onCompositionEnd$.next(undefined) - } - onInput() { - if (this._isDoingComposition) return - const [newState, typeInput] = this.#deduceInputFromTextAreaValue( - isMac() - ) - if ( - typeInput.replacePrevCharCnt === 0 && - typeInput.text.length === 1 && - isHighSurrogate(typeInput.text.charCodeAt(0)) - ) - return - this._textAreaState = newState - if (this._nextCommand === ReadFromTextArea.TYPE) { - if (typeInput.text !== '' || typeInput.replacePrevCharCnt !== 0) - this.onType$.next(typeInput) - } else { - if (typeInput.text !== '' || typeInput.replacePrevCharCnt !== 0) { - const metaData = new ClipboardMetaData() - metaData.text = typeInput.text - metaData.format = MIMES['.txt'] - const clipboardStoredMetaData = new ClipboardStoredMetaData() - clipboardStoredMetaData.clipboardMetaData = [metaData] - this.onPaste$.next(clipboardStoredMetaData) - } - this._nextCommand = ReadFromTextArea.TYPE - } - } - onCut(e: ClipboardEvent) { - this._textareaWrapper.setIgnoreSelectionChangeTime() - this._ensureClipboardGetsEditorSelection(e) - } - onCopy(e: ClipboardEvent) { - this._ensureClipboardGetsEditorSelection(e) - } - onPaste(e: ClipboardEvent) { - if (canUseTextData(e)) { - const data = getClipboardData(e) - this.onPaste$.next(data) - } else { - if ( - this._textareaWrapper.getSelectionStart() !== - this._textareaWrapper.getSelectionEnd() - ) - this._setAndWriteTextAreaState(TextAreaState.EMPTY) - this._nextCommand = ReadFromTextArea.PASTE - } - } - onFocus(e: FocusEvent) { - e.stopPropagation() - e.preventDefault() - // const hasFocus = this._hasFocus - this.#setHasFocus(true) - // if (isSafari() && !hasFocus && this._hasFocus) - // console.log('safari todo') - } - onBlur() { - if (this._isDoingComposition) { - this._isDoingComposition = false - this.writeScreenReaderContent() - this.onCompositionEnd$.next(undefined) - } - this.#setHasFocus(false) - } - private _textAreaState = TextAreaState.EMPTY - private _textareaWrapper: TextAreaWrapper - private _selectionChangeListener?: Subscription - private _nextCommand = ReadFromTextArea.TYPE - private _hasFocus = false - private _isDoingComposition = false - private _subs = new Subscription() - private _lastKeydown?: StandardKeyboardEvent - #deduceComposition = (text: string) => { - const oldState = this._textAreaState - const newState = TextAreaState.selectedText(text) - const typeInput = new TypeData() - typeInput.text = newState.value - typeInput.replacePrevCharCnt = - oldState.selectionEnd - oldState.selectionStart - return [newState, typeInput] as const - } - #deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean) => { - const oldState = this._textAreaState - const newState = TextAreaState.readFromTextArea(this._textareaWrapper) - return [ - newState, - TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput), - ] as const - } - - #setHasFocus(hasFocus: boolean): void { - if (this._hasFocus === hasFocus) return - this._hasFocus = hasFocus - if (this._selectionChangeListener) { - this._selectionChangeListener.unsubscribe() - this._selectionChangeListener = undefined - } - if (this._hasFocus) { - this.writeScreenReaderContent() - this._installSelectionChangeListener() - this.onFocus$.next(undefined) - } else this.onBlur$.next(undefined) - } - - private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void { - const start = this._textAreaState.selectionStartPosition - const end = this._textAreaState.selectionEndPosition - if (!start || !end) return - const dataToCopy = this.host.getDataToCopy(start, end) - if (!canUseTextData(e)) { - this._setAndWriteTextAreaState( - TextAreaState.selectedText(dataToCopy.text) - ) - return - } - setDataToCopy(dataToCopy.data, e) - } - - // tslint:disable-next-line: max-func-body-length - private _installSelectionChangeListener(): void { - let previoursSelectionChangeEventTime = 0 - this._selectionChangeListener = on( - document, - EventType.SELECTION_CHANGE - ).subscribe(() => { - if (!this._hasFocus || this._isDoingComposition || !isChrome()) - return - const now = Date.now() - const delta1 = now - previoursSelectionChangeEventTime - previoursSelectionChangeEventTime = now - if (delta1 < 5) return - const delta2 = - now - this._textareaWrapper.getIgnoreSelectionChangeTime() - this._textareaWrapper.resetSelectionChangeTime() - if (delta2 < 100) return - if ( - !this._textAreaState.selectionStartPosition || - !this._textAreaState.selectionEndPosition - ) - return - const newValue = this._textareaWrapper.getValue() - if (this._textAreaState.value !== newValue) return - const newSelectionStart = this._textareaWrapper.getSelectionStart() - const newSelectionEnd = this._textareaWrapper.getSelectionEnd() - if ( - newSelectionStart === this._textAreaState.selectionStart && - newSelectionEnd === this._textAreaState.selectionEnd - ) - return - const newStartPosition = - this._textAreaState.deduceEditorPosition(newSelectionStart) - const newSelectionStartPosition = this.host.deduceModelPosition( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - newStartPosition[0]!, - newStartPosition[1], - newStartPosition[2] - ) - const newEndPosition = - this._textAreaState.deduceEditorPosition(newSelectionEnd) - const newSelectionEndPosition = this.host.deduceModelPosition( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - newEndPosition[0]!, - newEndPosition[1], - newEndPosition[2] - ) - const newSelection = new Selection() - newSelection.selectionStartLineNumber = - newSelectionStartPosition.lineNumber - newSelection.selectionStartColumn = newSelectionStartPosition.column - newSelection.positionLineNumber = newSelectionEndPosition.lineNumber - newSelection.positionColumn = newSelectionEndPosition.column - this.onSelectionChange$.next(newSelection) - }) - } - - private _setAndWriteTextAreaState(textAreaState: TextAreaState): void { - let state = textAreaState - if (!this._hasFocus) state = textAreaState.collapseSelection() - state.writeToTextArea(this._textareaWrapper, this._hasFocus) - this._textAreaState = state - } -} diff --git a/src/components/textarea/input/textarea-state.ts b/src/components/textarea/input/textarea-state.ts deleted file mode 100644 index 672f0aab..00000000 --- a/src/components/textarea/input/textarea-state.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { - commonPrefixLength, - commonSuffixLength, - containsEmoji, - containsFullWidthCharactor, -} from '@/core/strings' -import {Position} from './position' -import {TextAreaWrapper} from './textarea-wrapper' -import {TypeData} from './type_data' -export class TextAreaState { - public value = '' - public selectionStart = 0 - public selectionEnd = 0 - public selectionStartPosition?: Position - public selectionEndPosition?: Position - static readonly EMPTY = new TextAreaState() - static readFromTextArea(textArea: TextAreaWrapper) { - const state = new TextAreaState() - state.value = textArea.getValue() - state.selectionStart = textArea.getSelectionStart() - state.selectionEnd = textArea.getSelectionEnd() - return state - } - - static selectedText(text: string) { - const state = new TextAreaState() - state.value = text - state.selectionEnd = text.length - return state - } - - static deduceInput( - previousState: TextAreaState, - currentState: TextAreaState, - couldBeEmojiInput: boolean - ) { - if (!previousState) return new TypeData() - let [ - prevValue, - prevSelectionStart, - prevSelectionEnd, - currValue, - currSelectionStart, - currSelectionEnd, - ] = [ - previousState.value, - previousState.selectionStart, - previousState.selectionEnd, - currentState.value, - currentState.selectionStart, - currentState.selectionEnd, - ] - const prevSuffix = prevValue.substring(prevSelectionEnd) - const currSuffix = currValue.substring(currSelectionEnd) - const suffixLength = commonSuffixLength(prevSuffix, currSuffix) - currValue = currValue.substring(0, currValue.length - suffixLength) - prevValue = prevValue.substring(0, prevValue.length - suffixLength) - - const prevPrefix = prevValue.substring(0, prevSelectionStart) - const currPrefix = currValue.substring(0, currSelectionStart) - const prefixLength = commonPrefixLength(prevPrefix, currPrefix) - currValue = currValue.substring(prefixLength) - prevValue = prevValue.substring(prefixLength) - currSelectionStart -= prefixLength - prevSelectionStart -= prefixLength - currSelectionEnd -= prefixLength - prevSelectionEnd -= prefixLength - if ( - couldBeEmojiInput && - currSelectionStart === currSelectionEnd && - prevValue.length > 0 - ) { - let potentialEmojiInput: string | null = null - if (currSelectionStart === currValue.length) { - if (currValue.startsWith(prevValue)) - potentialEmojiInput = currValue.substring(prevValue.length) - // tslint:disable-next-line: ext-curly - } else if (currValue.endsWith(prevValue)) - potentialEmojiInput = currValue.substring( - 0, - currValue.length - prevValue.length - ) - if ( - potentialEmojiInput !== null && - potentialEmojiInput.length > 0 && - (/\uFE0F/.test(potentialEmojiInput) || - containsEmoji(potentialEmojiInput)) - ) { - const typeData = new TypeData() - typeData.text = potentialEmojiInput - return typeData - } - } - if (currSelectionStart === currSelectionEnd) { - if ( - prevValue === currValue && - prevSelectionStart === 0 && - prevSelectionEnd === prevValue.length && - currSelectionStart === currValue.length && - currValue.indexOf('\n') === -1 - ) { - if (containsFullWidthCharactor(currValue)) return new TypeData() - } else { - const typeData = new TypeData() - typeData.text = currValue - typeData.replacePrevCharCnt = prevPrefix.length - prefixLength - return typeData - } - } - const replacePrevCharacters = prevSelectionEnd - prevSelectionStart - const typeData = new TypeData() - typeData.text = currValue - typeData.replacePrevCharCnt = replacePrevCharacters - return typeData - } - - collapseSelection(): TextAreaState { - const state = new TextAreaState() - state.value = this.value - state.selectionStart = this.value.length - state.selectionEnd = this.value.length - return state - } - - writeToTextArea(textArea: TextAreaWrapper, focus: boolean): void { - textArea.setValue(this.value) - if (!focus) return - textArea.setSelectionRange(this.selectionStart, this.selectionEnd) - } - - deduceEditorPosition( - offset: number - ): readonly [Position | undefined, number, number] { - if (offset <= this.selectionStart) { - const str = this.value.substring(offset, this.selectionStart) - return this._finishDeduceEditorPosition( - this.selectionStartPosition, - str, - -1 - ) - } - if (offset >= this.selectionEnd) { - const str = this.value.substring(this.selectionEnd, offset) - return this._finishDeduceEditorPosition( - this.selectionEndPosition, - str, - 1 - ) - } - const str1 = this.value.substring(this.selectionStart, offset) - if (str1.indexOf(String.fromCharCode(8230)) === -1) - return this._finishDeduceEditorPosition( - this.selectionStartPosition, - str1, - 1 - ) - const str2 = this.value.substring(offset, this.selectionEnd) - return this._finishDeduceEditorPosition( - this.selectionEndPosition, - str2, - -1 - ) - } - - private _finishDeduceEditorPosition( - anchor: Position | undefined, - deltaText: string, - signum: number - ): readonly [Position | undefined, number, number] { - let lineFeedCnt = 0 - let lastLineFeedIndex = -1 - while ( - (lastLineFeedIndex = deltaText.indexOf( - '\n', - lastLineFeedIndex + 1 - )) !== -1 - ) - lineFeedCnt += 1 - return [anchor, signum * deltaText.length, lineFeedCnt] - } -} - -export class PagedScreenReaderStrategy { - static fromEditorSelection(previousState: TextAreaState) { - previousState - return TextAreaState.EMPTY - } -} diff --git a/src/components/textarea/input/textarea-wrapper.ts b/src/components/textarea/input/textarea-wrapper.ts deleted file mode 100644 index bc8dc224..00000000 --- a/src/components/textarea/input/textarea-wrapper.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { - getParentScrollTop, - restoreParentsScrollTop, - getShadowRoot, -} from '@/core/document' -export class TextAreaWrapper { - constructor(public readonly textArea: HTMLTextAreaElement) {} - setIgnoreSelectionChangeTime(): void { - this._ignoreSelectionChangeTime = Date.now() - } - - getIgnoreSelectionChangeTime(): number { - return this._ignoreSelectionChangeTime - } - - resetSelectionChangeTime(): void { - this._ignoreSelectionChangeTime = 0 - } - - getValue(): string { - return this.textArea.value - } - - setValue(value: string): void { - if (this.textArea.value === value) return - this.setIgnoreSelectionChangeTime() - this.textArea.value = value - } - - getSelectionStart(): number { - return this.textArea.selectionDirection === 'backward' - ? this.textArea.selectionEnd - : this.textArea.selectionStart - } - - getSelectionEnd(): number { - return this.textArea.selectionDirection === 'backward' - ? this.textArea.selectionStart - : this.textArea.selectionEnd - } - - setSelectionRange(start: number, end: number): void { - const shadowRoot = getShadowRoot(this.textArea) - const activeElement = shadowRoot - ? shadowRoot.activeElement - : document.activeElement - const isFocused = activeElement === this.textArea - if ( - isFocused && - this.textArea.selectionStart === start && - this.textArea.selectionEnd === end - ) - return - if (isFocused) { - this.setIgnoreSelectionChangeTime() - this.textArea.setSelectionRange(start, end) - return - } - const scrollState = getParentScrollTop(this.textArea) - this.setIgnoreSelectionChangeTime() - this.textArea.focus() - this.textArea.setSelectionRange(start, end) - restoreParentsScrollTop(this.textArea, scrollState) - } - private _ignoreSelectionChangeTime = 0 -} diff --git a/src/components/textarea/input/type_data.ts b/src/components/textarea/input/type_data.ts deleted file mode 100644 index 1655c0d0..00000000 --- a/src/components/textarea/input/type_data.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class TypeData { - public text = '' - public replacePrevCharCnt = 0 - public replaceNextCharCnt = 0 - public positionDelta = 0 -} diff --git a/src/components/textarea/managers/cursor.ts b/src/components/textarea/managers/cursor.ts deleted file mode 100644 index 14d994be..00000000 --- a/src/components/textarea/managers/cursor.ts +++ /dev/null @@ -1,228 +0,0 @@ -import {BaseInfo} from '../cursor' -import {Text, Context} from '../defs' -import {TextManager} from './text' -import {KeyboardEventCode, StandardKeyboardEvent} from '@/core/events' -import {MouseEvent, useRef, useState} from 'react' -import {CursorEvent} from '../events' - -export const useCursor = (textMng: TextManager, context: Context) => { - // 将单元格内输入内容拉平成一行之后,算cursor的位置 - const getCursorInOneLine = () => { - const texts = textMng.getTwoDimensionalTexts() - let result = 0 - const {lineNumber, column} = currCursor.current - texts.forEach((eachLine, line) => { - if (line > lineNumber) return - if (line < lineNumber) { - const length = eachLine.reduce((a, b) => a + b.char.length, 0) - result += length - } else result += column - }) - return result - } - const getCursorInfoByOneLineCoordinate = (cursor: number) => { - const texts = textMng.getTwoDimensionalTexts() - let currIndex = 0 - let lineNumber = 0, - column = 0 - for (let line = 0; line < texts.length; line++) { - const eachLine = texts[line] - const lineLength = eachLine.reduce((a, b) => a + b.char.length, 0) - const index = lineLength + currIndex - if (index < cursor) { - currIndex += lineLength - continue - } else if (index >= cursor) { - lineNumber = line - column = cursor - currIndex - break - } else return - } - return getCursorInfoByCoordinate(lineNumber, column) - } - // 根据坐标获取光标的pixel位置 - const getCursorInfoByCoordinate = (line: number, column: number) => { - const baseInfo = new BaseInfo() - baseInfo.column = column - baseInfo.lineNumber = line - baseInfo.y = line * context.lineHeight() - const texts = textMng.getTwoDimensionalTexts() - let x = 0 - for (let i = 0; i < texts[line].length && i < column; i++) { - x += texts[line][i].width() - } - baseInfo.x = x - return baseInfo - } - /** - * 如果offsetX为-1,则cursorX为当前行的最后 - * 如果offsetY为-1,则cursorY为最后一行 - */ - const getCursorInfoByPosition = (offsetX: number, offsetY: number) => { - const lineNumber = Math.floor(offsetY / context.lineHeight()) - const baseInfo = new BaseInfo() - baseInfo.lineNumber = lineNumber - baseInfo.y = lineNumber * context.lineHeight() - const texts = textMng.getTwoDimensionalTexts() - if (texts.length === 0) return baseInfo - if (offsetY === -1) - baseInfo.y = (texts.length - 1) * context.lineHeight() - let currLineTexts = texts[lineNumber] - if (currLineTexts === undefined) { - const l = texts.length - 1 - baseInfo.lineNumber = l - baseInfo.y = l * context.lineHeight() - currLineTexts = texts[l] - } - if (offsetX === -1) { - let x = 0 - currLineTexts.forEach((t) => { - x += t.width() - }) - baseInfo.x = x - baseInfo.column = currLineTexts.length - return baseInfo - } - let column = 0 - let x = 0 - for (let i = 0; i < currLineTexts.length; i += 1) { - const t = currLineTexts[i] - if (t === undefined) continue - if (t.width() + x >= offsetX) { - const half = t.width() / 2 - if (x + half >= offsetX) column = i - else { - column = i + 1 - x += t.width() - } - break - } - x += t.width() - column = i + 1 - } - baseInfo.column = column - baseInfo.x = x - return baseInfo - } - const type = (added: readonly Text[], removed: readonly Text[]) => { - let x = currCursor.current.x - let y = currCursor.current.y - const [maxWidth] = textMng.getNewSize() - removed.forEach((t) => { - if (t.isEof) { - y -= context.lineHeight() - x = maxWidth - return - } - x -= t.width() - }) - added.forEach((t) => { - if (t.isEof) { - y += context.lineHeight() - x = 0 - return - } - x += t.width() - }) - _update(getCursorInfoByPosition(x, y)) - } - - const keydown = (e: StandardKeyboardEvent) => { - const {x: cursorX, y: cursorY, lineNumber, column} = currCursor.current - const texts = textMng.getTwoDimensionalTexts() - if (e.keyCodeId === KeyboardEventCode.ARROW_RIGHT) { - const next = texts[lineNumber][column] - if (next === undefined) return - const newCursor = getCursorInfoByPosition( - cursorX + next.width(), - cursorY - ) - if (newCursor.x === cursorX) return - _update(newCursor) - } else if (e.keyCodeId === KeyboardEventCode.ARROW_LEFT) { - if (column === 0) return - const last = texts[lineNumber][column - 1] - const newCursor = getCursorInfoByPosition( - cursorX - last.width(), - cursorY - ) - if (newCursor.x === cursorX) return - _update(newCursor) - } else if (e.keyCodeId === KeyboardEventCode.ARROW_DOWN) { - const next = texts[lineNumber + 1] - if (next === undefined) return - const newCursor = getCursorInfoByPosition( - cursorX, - cursorY + context.lineHeight() - ) - _update(newCursor) - } else if (e.keyCodeId === KeyboardEventCode.ARROW_UP) { - if (lineNumber === 0) return - const newCursor = getCursorInfoByPosition( - cursorX, - cursorY - context.lineHeight() - ) - _update(newCursor) - } else if (e.keyCodeId === KeyboardEventCode.ENTER) blur() - else if (e.keyCodeId === KeyboardEventCode.ESCAPE) blur() - } - - const focus = () => { - _update() - } - - const blur = () => { - const resetCursor = new BaseInfo() - _update(resetCursor) - } - - const mousedown = (e: MouseEvent) => { - const [x, y] = context.getOffset(e.clientX, e.clientY) - const cursor = getCursorInfoByPosition(x, y) - _update(cursor) - } - const setCursor = (cursor: number) => { - const cursorInfo = getCursorInfoByOneLineCoordinate(cursor) - _update(cursorInfo) - } - const _update = (cursor?: BaseInfo) => { - const cursorEvent = new CursorEvent() - cursorEvent.show = cursor !== undefined - if (cursor !== undefined) { - setCursorInfo(cursor) - currCursor.current = cursor - const clientX = cursor.x + context.clientX - const clientY = cursor.y + context.clientY - cursorEvent.clientX = clientX - cursorEvent.clientY = clientY - cursorEvent.columnNumber = cursor.column - cursorEvent.lineNumber = cursor.lineNumber - cursorEvent.offsetX = cursor.x - cursorEvent.offsetY = cursor.y - } - setCursorEvent(cursorEvent) - } - - const [cursorEvent$, setCursorEvent] = useState() - const [cursor$, setCursorInfo] = useState( - getCursorInfoByPosition( - context.textareaOffsetX, - context.textareaOffsetY - ) - ) - const currCursor = useRef(cursor$) - return { - cursorEvent$, - cursor$, - currCursor, - cursorHeight: context.cellHeight, - getCursorInfoByPosition, - type, - mousedown, - blur, - keydown, - focus, - getCursorInOneLine, - setCursor, - } -} diff --git a/src/components/textarea/managers/index.ts b/src/components/textarea/managers/index.ts deleted file mode 100644 index 888f384d..00000000 --- a/src/components/textarea/managers/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './text' -export * from './selection' -export * from './cursor' -export * from './selection' -export * from './input' -export * from './suggest' diff --git a/src/components/textarea/managers/input.ts b/src/components/textarea/managers/input.ts deleted file mode 100644 index c25fa25c..00000000 --- a/src/components/textarea/managers/input.ts +++ /dev/null @@ -1,189 +0,0 @@ -import {Context, Text} from '../defs' -import { - ClipboardMetaData, - ClipboardStoredMetaData, - KeyboardEventCode, - MIMES, -} from '@/core/events' -import {TextManager} from './text' -import { - Position, - ClipboardDataToCopy, - TextAreaInput, - TextAreaInputHost, - TextAreaState, - PagedScreenReaderStrategy, -} from '../input' -import {useCursor} from './cursor' -import {useSelection} from './selection' -import {AccessibilitySupport} from '@/core/document' -import {Subscription, Subject, Observable} from 'rxjs' - -export class InputManager extends Subscription { - constructor( - private readonly _textManager: TextManager, - private readonly _selectionManager: ReturnType, - private readonly _context: Context, - private readonly _cursorManager: ReturnType - ) { - super() - } - get textareaInput() { - return this._textareaInput - } - hasFocus(): boolean { - return this._textareaInput?.hasFocus() ?? false - } - setFocus() { - this._textareaInput?.focusTextArea() - this._cursorManager.focus() - } - - init(textarea: HTMLTextAreaElement) { - this._textareaInput?.destroy() - const textAreaInputHost: TextAreaInputHost = { - getDataToCopy: (start: Position, end: Position) => { - const texts = this._context.getTexts(start, end) - const d = new ClipboardMetaData() - d.format = MIMES['.txt'] - d.text = texts.join(this._context.eof) - const data = new ClipboardStoredMetaData([d]) - return new ClipboardDataToCopy(data) - }, - getScreenReaderContent: (currentState: TextAreaState) => { - if ( - this._accessibilitySupport === AccessibilitySupport.DISABLED - ) { - /** - * TODO(minglong): implement it - */ - // if (isMac()) - // console.log('TODO', 'mac process') - return TextAreaState.EMPTY - } - return PagedScreenReaderStrategy.fromEditorSelection( - currentState - ) - }, - deduceModelPosition: ( - viewAnchorPosition: Position, - deltaOffset: number, - lineFeedCnt: number - ) => { - /** - * TODO(minglong): implement it - */ - // console.log('TODO', viewAnchorPosition, deltaOffset, lineFeedCnt) - deltaOffset - lineFeedCnt - return viewAnchorPosition - }, - } - this._textareaInput = new TextAreaInput(textAreaInputHost, textarea) - this._listen() - } - - get blur$(): Observable { - return this._blur$ - } - private _blur$ = new Subject() - private _textareaInput?: TextAreaInput - private _accessibilitySupport = AccessibilitySupport.DISABLED - private get _cursor() { - return this._cursorManager.currCursor.current - } - - private _listen(): void { - const input = this._textareaInput - if (input === undefined) return - // this.add(this._textManager.textChanged$.subscribe(() => { - // input.writeScreenReaderContent() - // })) - this.add( - input.onBlur$.subscribe(() => { - this._blur$.next(undefined) - this._cursorManager.blur() - }) - ) - this.add( - input.onFocus$.subscribe(() => { - if (this.hasFocus()) return - this._cursorManager.focus() - }) - ) - this.add( - input.onType$.subscribe((e) => { - const cursor = this._cursor - - const selection = this._selectionManager.getSelection() - let added: readonly Text[] = [] - let removed: readonly Text[] = [] - if (selection !== undefined) { - removed = this._textManager.remove( - selection.startLineNumber, - selection.startColumn, - selection.endLineNumber, - selection.endColumn - ) - this._cursorManager.type([], removed) - } - if ( - e.positionDelta || - e.replaceNextCharCnt || - e.replacePrevCharCnt - ) - // console.log('textarea type composition change', e) - e - else { - const line = cursor.lineNumber - const col = cursor.column - added = this._textManager.add(e.text, line, col) - this._cursorManager.type(added, []) - } - input.writeScreenReaderContent() - this._selectionManager.type() - }) - ) - this.add( - input.onKeyDown$.subscribe((e) => { - if (e.keyCodeId === KeyboardEventCode.BACKSPACE) { - const cursor = this._cursor - if (cursor.lineNumber === 0 && cursor.column === 0) return - const selection = this._selectionManager.getSelection() - const removed: Text[] = [] - if (selection !== undefined) - removed.push( - ...this._textManager.remove( - selection.startLineNumber, - selection.startColumn, - selection.endLineNumber, - selection.endColumn - ) - ) - else - removed.push( - ...this._textManager.remove( - cursor.lineNumber, - cursor.column - 1, - cursor.lineNumber, - cursor.column - 1 - ) - ) - this._cursorManager.type([], removed) - this._selectionManager.keydown(e) - } else if (e.keyCodeId === KeyboardEventCode.ENTER) - this._blur$.next(undefined) - else this._cursorManager.keydown(e) - }) - ) - this.add( - input.onPaste$.subscribe((e) => { - const text = e.clipboardMetaData[0]?.text - if (text === undefined) return - const {lineNumber, column} = this._cursor - const added = this._textManager.add(text, lineNumber, column) - this._cursorManager.type(added, []) - }) - ) - } -} diff --git a/src/components/textarea/managers/selection.ts b/src/components/textarea/managers/selection.ts deleted file mode 100644 index 270f7652..00000000 --- a/src/components/textarea/managers/selection.ts +++ /dev/null @@ -1,130 +0,0 @@ -import {CanvasAttr, PainterService} from '@/core/painter' -import {BaseInfo} from '@/components/textarea/cursor' -import {useCursor} from './cursor' -import {Context} from '../defs' -import {TextManager} from './text' -import {StandardKeyboardEvent} from '@/core/events' -import {MouseEvent as ReactMouseEvent, useRef} from 'react' -export class Selection { - public startX = 0 - public startY = 0 - public startLineNumber = 0 - public startColumn = 0 - public endX = 0 - public endY = 0 - public endLineNumber = 0 - public endColumn = 0 -} - -export const useSelection = ( - cursorMng: ReturnType, - context: Context, - textMng: TextManager -) => { - const _painterSvc = new PainterService() - - const _startCursor = useRef() - const currSelection = useRef(new Selection()) - - const init = (canvas: HTMLCanvasElement) => { - _painterSvc.setupCanvas(canvas, 0, 0) - } - - const _initSelection = () => { - currSelection.current = new Selection() - _drawSelection() - } - const mousedown = (e: ReactMouseEvent) => { - const [x, y] = context.getOffset(e.clientX, e.clientY) - _startCursor.current = cursorMng.getCursorInfoByPosition(x, y) - _initSelection() - } - - const mousemove = (e: MouseEvent) => { - const [x, y] = context.getOffset(e.clientX, e.clientY) - let curr = cursorMng.getCursorInfoByPosition(x, y) - const startCursor = _startCursor.current - if (!curr || !startCursor) return - if (startCursor.biggerThan(curr)) { - const tmp = curr - curr = startCursor - _startCursor.current = tmp - } - const selection = new Selection() - selection.startX = startCursor.x - selection.startY = startCursor.y - selection.startColumn = startCursor.column - selection.startLineNumber = startCursor.lineNumber - selection.endX = curr.x - selection.endY = curr.y - selection.endColumn = curr.column - selection.endLineNumber = curr.lineNumber - if (!curr.equal(startCursor)) currSelection.current = selection - _drawSelection() - } - - const keydown = (e: StandardKeyboardEvent) => { - if (e.isKeyBinding) return - _painterSvc.clear() - _initSelection() - } - - const type = () => { - _initSelection() - } - - const getSelection = () => { - const sel = currSelection.current - if ( - sel.startColumn === sel.endColumn && - sel.startLineNumber === sel.endLineNumber - ) - return - return sel - } - - const _drawSelection = () => { - const selection = getSelection() - if (selection === undefined) return - const [totalWidth, totalHeight] = textMng.getNewSize() - _painterSvc.setupCanvas(undefined, totalWidth, totalHeight) - _painterSvc.save() - const selAttr = new CanvasAttr() - selAttr.fillStyle = 'rgba(0,0,0,0.38)' - _painterSvc.attr(selAttr) - const height = context.lineHeight() - const startLine = selection.startLineNumber - const endLine = selection.endLineNumber - if (startLine === endLine) { - const x = selection.startX - const y = selection.startY - const width = selection.endX - x - _painterSvc.fillRect(x, y, width, height) - return - } - for (let i = startLine; i <= endLine; i += 1) { - let x = 0 - const y = selection.startY + (i - startLine) * height - let width = 0 - if (i === startLine) { - x = selection.startX - width = totalWidth - selection.startX - } else { - x = 0 - width = i === endLine ? selection.endX : totalWidth - } - _painterSvc.fillRect(x, y, width, height) - } - _painterSvc.restore() - } - _drawSelection() - return { - selection: currSelection, - init, - getSelection, - mousedown, - mousemove, - keydown, - type, - } -} diff --git a/src/components/textarea/managers/suggest.ts b/src/components/textarea/managers/suggest.ts deleted file mode 100644 index 6617f538..00000000 --- a/src/components/textarea/managers/suggest.ts +++ /dev/null @@ -1,219 +0,0 @@ -import {useEffect, useRef, useState} from 'react' -import {Candidate, SpanItem} from '@/components/suggest' -import {lcsLenMatch} from '@/core/algo/lcs' -import { - fullFilterSnippet, - isFormula, - Snippet, - getAllFormulas, -} from '@/core/snippet' -import {TextManager} from './text' -import {useCursor} from './cursor' -import {SubType, TokenType} from '@/core/formula' -import {KeyboardEventCode, StandardKeyboardEvent} from '@/core/events' -import {TokenManager} from './token' - -export const useSuggest = ( - textMng: TextManager, - cursorMng: ReturnType -) => { - const [showSuggest$, setShowSuggest] = useState(false) - const [activeCandidate$, setActiveCandidate] = useState() - const [candidates$, setCandidates] = useState([]) - const replaceRange = useRef<{start: number; count: number}>() - - useEffect(() => { - // 监听光标移动,高亮用户可能需要输入的内容 - onTrigger(textMng.getPlainText()) - }, [cursorMng.cursor$]) - - const tokenMng = useRef(new TokenManager()) - - const onType = () => { - onTrigger(textMng.getPlainText()) - } - const onKeydown = (e: StandardKeyboardEvent) => { - if (!showSuggest$) return false - if (e.keyCodeId === KeyboardEventCode.ARROW_UP) { - if (!activeCandidate$) { - setActiveCandidate(candidates$[0]) - return true - } - const currIndex = candidates$.findIndex( - (c) => c === activeCandidate$ - ) - if (currIndex === -1) setActiveCandidate(candidates$[0]) - else - setActiveCandidate( - candidates$[currIndex === 0 ? 0 : currIndex - 1] - ) - return true - } else if (e.keyCodeId === KeyboardEventCode.ARROW_DOWN) { - if (!activeCandidate$) { - setActiveCandidate(candidates$[0]) - return true - } - const currIndex = candidates$.findIndex( - (c) => c === activeCandidate$ - ) - if (currIndex === -1) setActiveCandidate(candidates$[0]) - else - setActiveCandidate( - candidates$[ - currIndex === candidates$.length - 1 - ? currIndex - : currIndex + 1 - ] - ) - return true - } else if (e.keyCodeId === KeyboardEventCode.TAB) { - e.e.preventDefault() - if (activeCandidate$) onSuggest(activeCandidate$) - return true - } - return false - } - - const onTrigger = (text: string) => { - if (!shouldShowSuggest(text)) { - setShowSuggest(false) - return - } - replaceRange.current = undefined - const cursor = cursorMng.getCursorInOneLine() - const tokenManager = tokenMng.current - const tokenIndex = tokenManager.getTokenIndexByCursor(cursor, text) - const token = tokenManager.getToken(tokenIndex) - const newCandidates: Candidate[] = [] - if (!token) { - setShowSuggest(false) - return - } - // 如果当前光标停在function,则匹配function并提示 - if (tokenManager.isFunctionStart(token)) { - const candidates = fuzzyFilterFormula(token.value) - if (candidates.length === 0) { - setShowSuggest(false) - return - } - replaceRange.current = tokenManager.getTokenPosition(token) - newCandidates.push(...candidates) - // 如果光标停在参数位置或分隔符,计算当前属于第几个参数,提示该参数信息 - } else if (tokenManager.isOperandStart(token)) { - const {fnIndex, paramCount} = tokenManager.getFnInfo(token) - if (fnIndex === -1) { - setShowSuggest(false) - return - } - const fnName = tokenManager.getToken(fnIndex)?.value ?? '' - const snippet = fullFilterSnippet(fnName) - if (!snippet?.hasParams()) { - setShowSuggest(false) - return - } - let paramIndex = -1 - if (token.type === TokenType.OPERAND) - paramIndex = paramCount === 0 ? 0 : paramCount - 1 - else if (token.subtype === SubType.START) paramIndex = -1 - else if (token.subtype === SubType.SEPARATOR) - paramIndex = paramCount - const candidate = getParamCandidate(text, snippet, paramIndex) - newCandidates.push(...(candidate ? [candidate] : [])) - } - if (newCandidates.length === 0) { - setShowSuggest(false) - return - } - setShowSuggest(true) - setCandidates(newCandidates) - setActiveCandidate(newCandidates[0]) - } - const onSuggest = (candidate: Candidate) => { - setShowSuggest(false) - if (!replaceRange.current) return - const {start, count} = replaceRange.current - textMng.replace(candidate.plainText, start, count) - // 将光标设到函数括号中间 - if (candidate.quoteStart) { - const newCursor = start + candidate.quoteStart + 1 - cursorMng.setCursor(newCursor) - } - } - - return { - onType, - setShowSuggest, - onKeydown, - onSuggest, - showSuggest$, - candidates$, - activeCandidate$, - } -} -export interface ReplaceEvent { - range: {start: number; end: number} - text: string -} - -function shouldShowSuggest(text: string) { - return isFormula(text) -} -function getParamCandidate( - triggerText: string, - snippet: Snippet, - paramIndex: number -) { - const candidate = new Candidate(triggerText) - candidate.desc = snippet.args.at(paramIndex)?.description ?? '' - candidate.textOnly = true - const [msg, {startIndex, endIndex}] = snippet.getSnippetMessage(paramIndex) - const spans: SpanItem[] = [] - if (startIndex !== -1 && endIndex !== -1) { - const startSpan = new SpanItem(msg.slice(0, startIndex)) - const highlightSpan = new SpanItem( - msg.slice(startIndex, endIndex), - true - ) - const endSpan = new SpanItem(msg.slice(endIndex)) - spans.push(startSpan, highlightSpan, endSpan) - } else spans.push(new SpanItem(msg)) - candidate.spans = spans - return candidate -} - -function fuzzyFilterFormula(key: string) { - const result: Candidate[] = [] - const formulas = getAllFormulas() - const lcsResult = lcsLenMatch(key, formulas, (f) => f.name, false) - lcsResult.forEach((beMatchedInfo) => { - const quoteStart = beMatchedInfo.beMatched.name.length - const candidate = new Candidate( - key, - quoteStart, - `${beMatchedInfo.beMatched.name}()` - ) - candidate.desc = beMatchedInfo.beMatched.description - const spans: SpanItem[] = [] - let currIndex = 0 - const snippetMessage = beMatchedInfo.beMatched.name - beMatchedInfo.matchedMap.forEach((nameIndex) => { - const nameSlice = snippetMessage.slice(currIndex, nameIndex) - if (nameSlice !== '') { - const normalSpan = new SpanItem(nameSlice) - spans.push(normalSpan) - } - const highlightSlice = snippetMessage.substr(nameIndex, 1) - const highlightSpan = new SpanItem(highlightSlice, true) - spans.push(highlightSpan) - currIndex = nameIndex + highlightSlice.length - }) - const lastSlice = snippetMessage.substr(currIndex) - if (lastSlice !== '') { - const lastSpan = new SpanItem(lastSlice) - spans.push(lastSpan) - } - candidate.spans = spans - result.push(candidate) - }) - return result -} diff --git a/src/components/textarea/store/cursor.spec.ts b/src/components/textarea/store/cursor.spec.ts new file mode 100644 index 00000000..60a1b8b5 --- /dev/null +++ b/src/components/textarea/store/cursor.spec.ts @@ -0,0 +1,70 @@ +// @ts-nocheck +import {Cursor} from './cursor' +import { TextManager } from './text' +import {vi} from 'vitest' + +vi.mock('@/core/standable', async (asyncImport) => { + const mod = await asyncImport() + return { + ...mod, + StandardFont: class StandardFont { + setSize() { + return this + } + measureText() { + return { + width: 1 + } + } + } + } +}) + + + +describe('cursor test', () => { + test('_getCursorInOneLine', () => { + const cursor = new Cursor({ + textManager: new TextManager({ + context: { + text: '123\n456', + eof: '\n' + } + }), + }) + cursor.column = 2 + expect(cursor.cursorPosition).toBe(2) + + cursor.lineNumber = 1 + cursor.column = 1 + expect(cursor.cursorPosition).toBe(5) + }) + test('type', () => { + const cursor = new Cursor({ + textManager: new TextManager({ + context: { + text: '1234', + eof: '\n' + } + }), + }) + cursor.column = 1 + cursor.x = 1 + cursor.type([1], []) + expect(cursor.column).toBe(2) + expect(cursor.x).toBe(6) + }) + + test('updatePosition', () => { + const cursor = new Cursor({ + textManager: new TextManager({ + context: { + text: '123456789', + eof: '\n' + } + }), + }) + cursor.updatePosition(1) + expect(cursor.column).toBe(1) + }) +}) diff --git a/src/components/textarea/store/cursor.ts b/src/components/textarea/store/cursor.ts new file mode 100644 index 00000000..a9e36bf0 --- /dev/null +++ b/src/components/textarea/store/cursor.ts @@ -0,0 +1,227 @@ +import { action, computed, makeObservable, observable } from 'mobx' +import { Text } from '../defs' +import { BaseInfo } from '../cursor' +import type { TextareaStore } from './store' +import { Subject } from 'rxjs' + +export class Cursor { + constructor(public readonly store: TextareaStore) { + makeObservable(this) + } + cursor$ = new Subject() + + compositionStart = -1 + + @computed + get cursorPosition () { + return this._getCursorInOneLine() + } + + @observable + /** + * update it by this.updatePostion + */ + showCursor = false + + @observable + /** + * update it by this.updatePostion + */ + x = 0 + + @observable + /** + * update it by this.updatePostion + */ + y = 0 + + @observable + /** + * update it by this.updatePostion + */ + lineNumber = 0 + + @observable + /** + * update it by this.updatePostion + */ + column = 0 + toBaseInfo () { + return new BaseInfo() + .setColumn(this.column) + .setLineNumber(this.lineNumber) + .setX(this.x) + .setY(this.y) + } + + + @action + focus () { + this.updatePosition(0) + this.showCursor = true + } + + @action + blur () { + this.showCursor = false + } + + @action + type (added: readonly Text[], removed: readonly Text[]) { + let currPosition = this.cursorPosition + if (this.store.selection?.selection) { + const {startColumn, startLine, startX, startY} = this.store.selection.selection + this._updateByCursorInfo(new BaseInfo() + .setColumn(startColumn) + .setLineNumber(startLine) + .setX(startX) + .setY(startY) + ) + return + } else currPosition -= removed.length + currPosition += added.length + this.updatePosition(currPosition) + } + + @action + mousedown (e: MouseEvent) { + const [x, y] = this.store.context.getOffset(e.clientX, e.clientY) + const cursor = this.getCursorInfoByPosition(x, y) + this._updateByCursorInfo(cursor) + } + + @action + updatePosition (cursorPosition: number) { + const info = this.getCursorInfoByOneLineCoordinate(cursorPosition) + this._updateByCursorInfo(info) + } + + @action + updateTwoDimensionalPosition (line: number, column: number) { + const baseInfo = new BaseInfo() + + const texts = this.store.textManager.getTwoDimensionalTexts() + baseInfo.setLineNumber(Math.min(line, texts.length - 1)) + + for (let i = 0; i < texts.length; i++) { + const t = texts[i] + if (i !== line) { + continue + } + baseInfo.setColumn(Math.min(t.length, column)) + baseInfo.setX(t.slice(0, baseInfo.column).reduce((pre, cur) => pre + cur.width(), 0)) + } + baseInfo.setY(baseInfo.lineNumber * this.store.context.lineHeight()) + + this._updateByCursorInfo(baseInfo) + } + + /** + * @param offsetX -1 represent the last column of current line + * @param offsetY -1 represent the last line + */ + getCursorInfoByPosition (offsetX: number, offsetY: number) { + const baseHeight = this.store.context.lineHeight() + const lineNumber = Math.floor(offsetY / baseHeight) + const baseInfo = new BaseInfo().setLineNumber(lineNumber).setY(lineNumber * baseHeight) + const texts = this.store.textManager.getTwoDimensionalTexts() + if (texts.length === 0) return baseInfo + if (offsetY === -1) + baseInfo.setY((texts.length - 1) * baseHeight) + let currLineTexts = texts[lineNumber] + if (currLineTexts === undefined) { + const l = texts.length - 1 + baseInfo.setLineNumber(l).setY(l * baseHeight) + currLineTexts = texts[l] + } + if (offsetX === -1) { + let x = 0 + currLineTexts.forEach((t) => { + x += t.width() + }) + baseInfo.setX(x).setColumn(currLineTexts.length) + return baseInfo + } + let column = 0 + let x = 0 + for (let i = 0; i < currLineTexts.length; i += 1) { + const t = currLineTexts[i] + if (t === undefined) continue + if (t.width() + x >= offsetX) { + const half = t.width() / 2 + if (x + half >= offsetX) column = i + else { + column = i + 1 + x += t.width() + } + break + } + x += t.width() + column = i + 1 + } + return baseInfo + .setColumn(column) + .setX(x) + } + + @action + private _updateByCursorInfo (info: BaseInfo) { + this.x = info.x + this.y = info.y + this.lineNumber = info.lineNumber + this.column = info.column + this.cursor$.next(undefined) + } + + private _getCursorInfoByCoordinate (line: number, column: number) { + const texts = this.store.textManager.getTwoDimensionalTexts() + const baseInfo = new BaseInfo() + .setColumn(column) + .setLineNumber(line) + .setY(line * this.store.context.lineHeight()) + let x = 0 + for (let i = 0; i < texts[line].length && i < column; i++) { + x += texts[line][i].width() + } + baseInfo.x = x + return baseInfo + } + + private _getCursorInOneLine () { + let result = 0 + let line = 0 + const texts = this.store.textManager.texts + for (let i = 0; i < texts.length; i++) { + const t = texts[i] + if (this.lineNumber === line) { + result += this.column + break + } else { + result += 1 + line += (t.isEof ? 1 : 0) + } + } + return result + } + private getCursorInfoByOneLineCoordinate (cursor: number) { + const baseInfo = new BaseInfo() + const texts = this.store.textManager.texts + for (let i = 0; i < texts.length; i++) { + if (i === cursor) break + const t = texts[i] + if (t.isEof) { + baseInfo + .setY(baseInfo.y + this.store.context.lineHeight()) + .setX(0) + .setLineNumber(baseInfo.lineNumber + 1) + .setColumn(0) + } else { + baseInfo + .setX(baseInfo.x + t.width()) + .setColumn(baseInfo.column + 1) + } + } + return baseInfo + } + +} \ No newline at end of file diff --git a/src/components/textarea/store/index.ts b/src/components/textarea/store/index.ts new file mode 100644 index 00000000..1beb0255 --- /dev/null +++ b/src/components/textarea/store/index.ts @@ -0,0 +1,3 @@ +export * from './store' +export * from './cursor' +export * from './text' \ No newline at end of file diff --git a/src/components/textarea/store/selection.ts b/src/components/textarea/store/selection.ts new file mode 100644 index 00000000..138adc82 --- /dev/null +++ b/src/components/textarea/store/selection.ts @@ -0,0 +1,135 @@ +import { makeObservable, observable } from "mobx"; +import { TextareaStore } from "./store"; +import { CanvasAttr, PainterService } from "@/core/painter"; +import { BaseInfo } from "../cursor"; + +export class Selection { + constructor(public readonly store: TextareaStore) { + makeObservable(this) + } + + selection?: SelectionInfo + + clear() { + this.selection = undefined + this._painterSvc.clear() + } + + init(selectionCanvas: HTMLCanvasElement) { + this._painterSvc.setupCanvas(selectionCanvas) + this.clear() + } + + mousemove(e: MouseEvent) { + const [x, y] = this.store.context.getOffset(e.clientX, e.clientY) + const curr = this.store.cursor.getCursorInfoByPosition(x, y) + const start = this.store.cursor.toBaseInfo() + const baseHeight = this.store.context.lineHeight() + + let _start: BaseInfo + let _end: BaseInfo + if (curr.lineNumber > start.lineNumber) { + _start = start + _end = curr + } else if (curr.lineNumber < start.lineNumber) { + _start = curr + _end = start + } else { + if (curr.column <= start.column) { + _start = curr + _end = start + } else { + _start = start + _end = curr + } + } + this.selection = new SelectionInfo() + .setStartX(_start.x) + .setStartY(_start.y) + .setStartColumn(_start.column) + .setStartLine(_start.lineNumber) + .setEndX(_end.x) + .setEndY(_end.y + baseHeight) + .setEndColumn(_end.column) + .setEndLine(_end.lineNumber) + this._drawSelection() + } + + private _drawSelection () { + if (this.selection === undefined) return + const [totalWidth, totalHeight] = this.store.textManager.getNewSize() + this._painterSvc.setupCanvas(undefined, totalWidth, totalHeight) + this._painterSvc.save() + const attr = new CanvasAttr() + attr.fillStyle = 'rgba(0,0,0,0.3)' + this._painterSvc.attr(attr) + const height = this.store.context.lineHeight() + const {startX, startY, endX, startLine, endLine} = this.selection + if (startLine === endLine) { + const width = endX - startX + this._painterSvc.fillRect(startX, startY, width, height) + return + } + for (let i = startLine; i <= endLine; i += 1) { + let x = 0 + const y = this.selection.startY + (i - startLine) * height + let width = 0 + if (i === startLine) { + x = this.selection.startX + width = totalWidth - this.selection.startX + } else { + x = 0 + width = i === endLine ? this.selection.endX : totalWidth + } + this._painterSvc.fillRect(x, y, width, height) + } + this._painterSvc.restore() + } + + + + private _painterSvc = new PainterService() +} + +export class SelectionInfo { + setStartX(x: number) { + this.startX = x + return this + } + setStartY(y: number) { + this.startY = y + return this + } + setEndX(x: number) { + this.endX = x + return this + } + setEndY(y: number) { + this.endY = y + return this + } + setStartLine(line: number) { + this.startLine = line + return this + } + setStartColumn(column: number) { + this.startColumn = column + return this + } + setEndLine(line: number) { + this.endLine = line + return this + } + setEndColumn(column: number) { + this.endColumn = column + return this + } + startLine = 0 + startColumn = 0 + endLine = 0 + endColumn = 0 + startX = 0 + startY = 0 + endX = 0 + endY = 0 +} diff --git a/src/components/textarea/store/store.ts b/src/components/textarea/store/store.ts new file mode 100644 index 00000000..c56420ff --- /dev/null +++ b/src/components/textarea/store/store.ts @@ -0,0 +1,34 @@ +import { action, makeObservable, observable } from "mobx"; +import { createContext } from "react"; +import { Cursor } from "./cursor"; +import { Context } from "../defs"; +import { TextManager } from "./text"; +import {Suggest} from './suggest' +import {Selection} from './selection' + +export class TextareaStore { + constructor(public readonly context: Context) { + makeObservable(this) + this.textManager = new TextManager(this) + this.cursor = new Cursor(this) + this.suggest = new Suggest(this) + this.selection = new Selection(this) + } + @observable + isComposing = false + + isMousedown = false + + @action + setComposing(isComposing: boolean) { + this.isComposing = isComposing + } + + selection: Selection + cursor: Cursor + textManager: TextManager + suggest: Suggest +} + +// @ts-expect-error inject data when use context +export const TextareaContext = createContext({}) \ No newline at end of file diff --git a/src/components/textarea/store/suggest.ts b/src/components/textarea/store/suggest.ts new file mode 100644 index 00000000..d548ac3f --- /dev/null +++ b/src/components/textarea/store/suggest.ts @@ -0,0 +1,172 @@ +import { action, computed, makeObservable, observable } from "mobx"; +import { TextareaStore } from "./store"; +import { Candidate, SpanItem } from "@/components/suggest"; +import { lcsLenMatch } from "@/core/algo/lcs"; +import { isFormula, Snippet, getAllFormulas, fullFilterSnippet } from "@/core/snippet"; +import { TokenType, SubType } from "@/core/formula"; +import { TokenManager } from "./token"; + +export class Suggest { + constructor(public readonly store: TextareaStore) { + makeObservable(this) + } + @computed + get activeCandidate() { + return this._activeCandidate + } + + set activeCandidate(active: number) { + let _active = active + if (_active < 0) _active = 0 + if (_active >= this.candidates.length - 1) _active = this.candidates.length - 1 + this._activeCandidate = _active + } + @observable + showSuggest = false + + @observable + candidates: Candidate[] = [] + + + @action + onType () { + this.onTrigger(this.store.textManager.getPlainText()) + } + + @action + onSuggest = (active: number = this._activeCandidate) => { + this.showSuggest = false + if (!this.replaceRange) return + const {start, count} = this.replaceRange + const candidate = this.candidates[active] + this.store.textManager.replace(candidate.plainText, start, count) + // set cursor between brace + if (candidate.quoteStart) { + const newCursor = start + candidate.quoteStart + 1 + this.store.cursor.updatePosition(newCursor) + } + } + + @observable + private _activeCandidate = -1 + private _tokenManager = new TokenManager() + private replaceRange?: {start: number; count: number} + + @action + private onTrigger (text: string) { + if (!shouldShowSuggest(text)) { + this.showSuggest = false + return + } + this.replaceRange = undefined + const cursor = this.store.cursor.cursorPosition + const tokenIndex = this._tokenManager.getTokenIndexByCursor(cursor, text) + const token = this._tokenManager.getToken(tokenIndex) + const newCandidates: Candidate[] = [] + if (!token) { + this.showSuggest = false + return + } + // 如果当前光标停在function,则匹配function并提示 + if (this._tokenManager.isFunctionStart(token)) { + const candidates = fuzzyFilterFormula(token.value) + if (candidates.length === 0) { + this.showSuggest = false + return + } + this.replaceRange = this._tokenManager.getTokenPosition(token) + newCandidates.push(...candidates) + // 如果光标停在参数位置或分隔符,计算当前属于第几个参数,提示该参数信息 + } else if (this._tokenManager.isOperandStart(token)) { + const {fnIndex, paramCount} = this._tokenManager.getFnInfo(token) + if (fnIndex === -1) { + this.showSuggest = false + return + } + const fnName = this._tokenManager.getToken(fnIndex)?.value ?? '' + const snippet = fullFilterSnippet(fnName) + if (!snippet?.hasParams()) { + this.showSuggest = false + return + } + let paramIndex = -1 + if (token.type === TokenType.OPERAND) + paramIndex = paramCount === 0 ? 0 : paramCount - 1 + else if (token.subtype === SubType.START) paramIndex = -1 + else if (token.subtype === SubType.SEPARATOR) + paramIndex = paramCount + const candidate = getParamCandidate(text, snippet, paramIndex) + newCandidates.push(...(candidate ? [candidate] : [])) + } + if (newCandidates.length === 0) { + this.showSuggest = false + return + } + this.showSuggest = true + this.candidates = newCandidates + this.activeCandidate = 0 + } +} + +function shouldShowSuggest(text: string) { + return isFormula(text) +} +function getParamCandidate( + triggerText: string, + snippet: Snippet, + paramIndex: number +) { + const candidate = new Candidate(triggerText) + candidate.desc = snippet.args.at(paramIndex)?.description ?? '' + candidate.textOnly = true + const [msg, {startIndex, endIndex}] = snippet.getSnippetMessage(paramIndex) + const spans: SpanItem[] = [] + if (startIndex !== -1 && endIndex !== -1) { + const startSpan = new SpanItem(msg.slice(0, startIndex)) + const highlightSpan = new SpanItem( + msg.slice(startIndex, endIndex), + true + ) + const endSpan = new SpanItem(msg.slice(endIndex)) + spans.push(startSpan, highlightSpan, endSpan) + } else spans.push(new SpanItem(msg)) + candidate.spans = spans + return candidate +} + +function fuzzyFilterFormula(key: string) { + const result: Candidate[] = [] + const formulas = getAllFormulas() + const lcsResult = lcsLenMatch(key, formulas, (f) => f.name, false) + lcsResult.forEach((beMatchedInfo) => { + const quoteStart = beMatchedInfo.beMatched.name.length + const candidate = new Candidate( + key, + quoteStart, + `${beMatchedInfo.beMatched.name}()` + ) + candidate.desc = beMatchedInfo.beMatched.description + const spans: SpanItem[] = [] + let currIndex = 0 + const snippetMessage = beMatchedInfo.beMatched.name + beMatchedInfo.matchedMap.forEach((nameIndex) => { + const nameSlice = snippetMessage.slice(currIndex, nameIndex) + if (nameSlice !== '') { + const normalSpan = new SpanItem(nameSlice) + spans.push(normalSpan) + } + const highlightSlice = snippetMessage.substr(nameIndex, 1) + const highlightSpan = new SpanItem(highlightSlice, true) + spans.push(highlightSpan) + currIndex = nameIndex + highlightSlice.length + }) + const lastSlice = snippetMessage.substr(currIndex) + if (lastSlice !== '') { + const lastSpan = new SpanItem(lastSlice) + spans.push(lastSpan) + } + candidate.spans = spans + result.push(candidate) + }) + return result +} diff --git a/src/components/textarea/store/text.spec.ts b/src/components/textarea/store/text.spec.ts new file mode 100644 index 00000000..ef5e735c --- /dev/null +++ b/src/components/textarea/store/text.spec.ts @@ -0,0 +1,36 @@ +// @ts-nocheck +import {TextManager} from './text' +vi.mock('@/core/standable', async (asyncImport) => { + const mod = await asyncImport() + return { + ...mod, + StandardFont: class StandardFont { + setSize() { + return this + } + measureText() { + return { + width: 1 + } + } + } + } +}) + +describe('text test', () => { + let manager: TextManager + beforeEach(() => { + manager = new TextManager({context: {text: '1234', eof: '\n', lineHeight: () => 1}}) + vi.spyOn(manager, 'drawText').mockImplementation(() => {}) + }) + test('remove one item', () => { + const removed = manager.remove(2, 2) + expect(removed.length).toBe(1) + expect(removed[0].char).toBe('3') + }) + test('remove last', () => { + const removed = manager.remove(3, 3) + expect(removed.length).toBe(1) + expect(removed[0].char).toBe('4') + }) +}) \ No newline at end of file diff --git a/src/components/textarea/managers/text.ts b/src/components/textarea/store/text.ts similarity index 54% rename from src/components/textarea/managers/text.ts rename to src/components/textarea/store/text.ts index c3e9ff81..69cbc1e5 100644 --- a/src/components/textarea/managers/text.ts +++ b/src/components/textarea/store/text.ts @@ -1,18 +1,25 @@ -import {Text, Texts, History, Context} from '../defs' +import {Text, Texts, History} from '../defs' import {Box, PainterService, TextAttr} from '@/core/painter' import {Range} from '@/core/standable' -import {BehaviorSubject, Observable} from 'rxjs' -export class TextManager { - constructor(public readonly context: Context) { - this._texts = Texts.from(this.context.text, this.context.eof) - this._textChange$.next(this.getPlainText()) +import type {TextareaStore} from './store' + +export interface ITwoDimensionalInfo { + startLine: number + startColumn: number + endLine: number + endColumn: number +} + +export class TextManager { + constructor(public readonly store: TextareaStore) { + this._texts = Texts.from(this.store.context.text, this.store.context.eof) } - textChanged(): Observable { - return this._textChange$ + get texts() { + return this._texts.texts } getNewSize(): readonly [width: number, height: number] { const texts = this.getTwoDimensionalTexts() - const baseHeight = this.context.lineHeight() + const baseHeight = this.store.context.lineHeight() const height = baseHeight * texts.length const widths = texts.map((ts) => ts.map((t) => t.width()).reduce((p, c) => p + c) @@ -25,26 +32,36 @@ export class TextManager { const [width, height] = this.getNewSize() const paddingRight = 10 this._painterSvc.setupCanvas(canvas, width + paddingRight, height) - const texts = this.getTwoDimensionalTexts() - const baseHeight = this.context.lineHeight() - texts.forEach((ts, i) => { - const y = i * baseHeight - let x = 0 - ts.forEach((t) => { - const position = new Range() - .setStartRow(y) - .setEndRow(y + this.context.lineHeight()) - .setStartCol(x) - .setEndCol(x + t.width() + 2) - const box = new Box() - box.position = position - const attr = new TextAttr() - attr.setFont(t.font) - this._painterSvc.text(t.char, attr, box) - x += t.width() - }) + const baseHeight = this.store.context.lineHeight() + let y = 0 + let x = 0 + this._texts.texts.forEach((text) => { + const position = new Range() + .setStartRow(y) + .setEndRow(y + baseHeight) + .setStartCol(x) + .setEndCol(x + text.width() + 2) + const box = new Box().setPosition(position) + const attr = new TextAttr() + attr.setFont(text.font) + this._painterSvc.text(text.char, attr, box) + if (text.isEof) { + y += baseHeight + x = 0 + this._painterSvc.text( + '', + new TextAttr(), + new Box().setPosition( + new Range() + .setStartRow(y) + .setEndRow(y + baseHeight) + .setStartCol(x) + .setEndCol(x) + )) + } else { + x += text.width() + } }) - this._textChange$.next(this.getPlainText()) } /** @@ -82,29 +99,22 @@ export class TextManager { return texts.getPlainText() } - remove( - startLine: number, - startColumn: number, - endLine: number, - endColumn: number - ) { - const [start, end] = this._twoDimensionalToOneDimensinal( - startLine, - startColumn, - endLine, - endColumn - ) + remove(start: number, end: number) { const r = this._texts.remove(start, end) this.drawText() return r } + removeInTwoDimensional(info: ITwoDimensionalInfo) { + const [start, end] = this._twoDimensionalToOneDimensinal(info) + return this.remove(start, end) + } replace( content: string, start: number, count: number ): readonly [added: readonly Text[], removed: readonly Text[]] { - const eof = this.context.eof + const eof = this.store.context.eof const newTexts = Texts.from(content, eof) const removed = this._texts.replace(newTexts, start, count) this._history.add(this._texts) @@ -112,45 +122,27 @@ export class TextManager { return [newTexts.texts, removed] } - add(content: string, line: number, column: number) { - const eof = this.context.eof + add(content: string, line?: number, column?: number) { + const l = line ?? this.store.cursor.lineNumber + const c = column ?? this.store.cursor.column + const eof = this.store.context.eof const newTexts = Texts.from(content, eof) - const [start] = this._twoDimensionalToOneDimensinal( - line, - column, - line, - column - ) + const [start] = this._twoDimensionalToOneDimensinal({ + startLine: l, + endLine: l, + startColumn: c, + endColumn: c + }) this._texts.add(newTexts, start) this.drawText() return newTexts.texts } - getText( - startLine: number, - startColumn: number, - endLine: number, - endColumn: number - ): readonly Text[] { - const [start, end] = this._twoDimensionalToOneDimensinal( - startLine, - startColumn, - endLine, - endColumn - ) - return this._texts.texts.slice(start, end) - } - // private _textChanged$ = new Subject() private _texts = new Texts() private _history = new History() private _painterSvc = new PainterService() - private _textChange$ = new BehaviorSubject('') - private _twoDimensionalToOneDimensinal( - startLine: number, - startColumn: number, - endLine: number, - endColumn: number - ): readonly [start: number, end: number] { + private _twoDimensionalToOneDimensinal(props: ITwoDimensionalInfo): readonly [start: number, end: number] { + const {startColumn, startLine, endColumn, endLine} = props const texts = this.getTwoDimensionalTexts() if (texts.length === 0) return [0, 0] let [start, end] = [0, 0] diff --git a/src/components/textarea/managers/token.test.ts b/src/components/textarea/store/token.test.ts similarity index 100% rename from src/components/textarea/managers/token.test.ts rename to src/components/textarea/store/token.test.ts diff --git a/src/components/textarea/managers/token.ts b/src/components/textarea/store/token.ts similarity index 100% rename from src/components/textarea/managers/token.ts rename to src/components/textarea/store/token.ts diff --git a/src/components/textarea/textarea.module.scss b/src/components/textarea/textarea.module.scss index fdeb0f7b..c50a109c 100644 --- a/src/components/textarea/textarea.module.scss +++ b/src/components/textarea/textarea.module.scss @@ -13,6 +13,7 @@ } .selection-canvas { position: absolute; + background-color: transparent; } .input-text-cursor { position: absolute; diff --git a/src/core/events/keyboard.ts b/src/core/events/keyboard.ts index b49ac555..a5adc99c 100644 --- a/src/core/events/keyboard.ts +++ b/src/core/events/keyboard.ts @@ -15,39 +15,34 @@ export class StandardKeyboardEvent { keyCodeId: KeyboardEventCode isKeyBinding: boolean isComposing: boolean + getInputValue() { + if (this.keyCodeId.startsWith('Key')) { + const base = this.keyCodeId.at(-1) ?? '' + return this.isUpperCase() ? base.toUpperCase() : base.toLowerCase() + } + return '' + } + isUpperCase() { + return this.e.getModifierState('CapsLock') + } isAlt() { - return isAlt(this.keyCodeId) + return isAlt(this.keyCodeId) || this.e.altKey } isCtrl() { - return isCtrl(this.keyCodeId) + return isCtrl(this.keyCodeId) || this.e.ctrlKey } isShift() { - return isShift(this.keyCodeId) + return isShift(this.keyCodeId) || this.e.shiftKey } isMeta() { return isMeta(this.keyCodeId) } - // private _computeKeyBinding() { - // let key = KeyboardEventCode.UNKNOWN - // if (!isKeyBinding(this.keyCodeId)) - // key = this.keyCodeId - // let result = 0 - // if (this.e.ctrlKey) - // result |= isMac() ? KeyMod.WIN_CTRL : KeyMod.CTRL_CMD - // if (this.e.altKey) - // result |= KeyMod.ALT - // if (this.e.shiftKey) - // result |= KeyMod.SHIFT - // if (this.e.metaKey) - // result |= isMac() ? KeyMod.CTRL_CMD : KeyMod.WIN_CTRL - // result |= key - // return result - // } } + function isKeyBinding(id: KeyboardEventCode) { return isCtrl(id) || isShift(id) || isAlt(id) || isMeta(id) } diff --git a/src/core/painter/box.ts b/src/core/painter/box.ts index 6b23d109..495caeba 100644 --- a/src/core/painter/box.ts +++ b/src/core/painter/box.ts @@ -13,6 +13,11 @@ export class Box { return this.position.height } + setPosition(position: Range) { + this.position = position + return this + } + public textX( align?: AlignX | null ): readonly [tx: number, textAlign: CanvasTextAlign] { diff --git a/src/core/painter/canvas.ts b/src/core/painter/canvas.ts index f37589c2..a11586ea 100644 --- a/src/core/painter/canvas.ts +++ b/src/core/painter/canvas.ts @@ -48,7 +48,6 @@ export class CanvasApi { const h = height ?? c.getBoundingClientRect().height c.width = w * dpr() c.height = h * dpr() - c.style.backgroundColor = '#fff' const ctx = c.getContext('2d') if (!ctx) { useToast().toast('Unexpected error, please refresh website!') diff --git a/src/ui/contextmenu/index.tsx b/src/ui/contextmenu/index.tsx index 36ebfcd7..64e25fa7 100644 --- a/src/ui/contextmenu/index.tsx +++ b/src/ui/contextmenu/index.tsx @@ -1,5 +1,5 @@ -import {CSSProperties, MouseEvent} from 'react' -import {DialogComponent, DialogProps} from '@/ui/dialog' +import { CSSProperties, MouseEvent } from 'react' +import { DialogComponent, DialogProps } from '@/ui/dialog' import './contextmenu.scss' export interface ContextMenuProps extends DialogProps { items: readonly ContextMenuItem[] @@ -10,7 +10,7 @@ export interface ContextMenuProps extends DialogProps { } export const ContextMenuComponent = (props: ContextMenuProps) => { - const {items, mouseevent, close$} = props + const { items, mouseevent, close$ } = props const contentStyle: CSSProperties = { ...props.style?.content, left: mouseevent?.clientX, @@ -18,36 +18,34 @@ export const ContextMenuComponent = (props: ContextMenuProps) => { } const onClick = (item: ContextMenuItem) => { item.click() - close$() + close$?.() } - const content = ( -
- {items.map((item, key) => { - switch (item.type) { - case 'text': - return ( -
onClick(item)} - > - {item.text} -
- ) - case 'divider': - return
- default: - return
- } - })} -
- ) return ( + style={{ content: contentStyle }} + > +
+ {items.map((item, key) => { + switch (item.type) { + case 'text': + return ( +
onClick(item)} + > + {item.text} +
+ ) + case 'divider': + return
+ default: + return
+ } + })} +
+ ) } export type ContextMenuType = 'text' | 'divider' @@ -55,5 +53,5 @@ export class ContextMenuItem { public type: ContextMenuType = 'text' public text = '' // eslint-disable-next-line @typescript-eslint/no-empty-function - public click: () => void = () => {} + public click: () => void = () => { } } diff --git a/src/ui/dialog/index.tsx b/src/ui/dialog/index.tsx index f49f80cf..e1041e61 100644 --- a/src/ui/dialog/index.tsx +++ b/src/ui/dialog/index.tsx @@ -1,9 +1,8 @@ -import Modal, {Props} from 'react-modal' +import Modal, { Props } from 'react-modal' import './dialog.scss' export interface DialogProps extends Props { - readonly close$: () => void - readonly content?: any + readonly close$?: () => void } export const DialogComponent = (props: DialogProps) => { @@ -14,8 +13,6 @@ export const DialogComponent = (props: DialogProps) => { shouldCloseOnOverlayClick={true} onRequestClose={props.close$} {...props} - > - {props.content} - + /> ) } diff --git a/tsconfig.json b/tsconfig.json index 68c859c2..b9017684 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,7 @@ "noFallthroughCasesInSwitch": true, "noImplicitAny": true, "outDir": "./dist/", - "types": ["reflect-metadata", "jest", "node"], + "types": ["reflect-metadata", "vitest/globals", "node"], "paths": { "@/*": ["src/*"], "@logisheets_bg": ["packages/web"] diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000..c81dec75 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,11 @@ +import { resolve } from 'path' +import {defineConfig} from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + alias: { + '@': resolve('src') + } + } +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f2e73cb4..91ef122a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1310,6 +1310,121 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + "@eslint/eslintrc@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" @@ -1488,6 +1603,13 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" @@ -1559,6 +1681,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== +"@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -1789,11 +1916,91 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/rollup-android-arm-eabi@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz#57936f50d0335e2e7bfac496d209606fa516add4" + integrity sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w== + +"@rollup/rollup-android-arm64@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz#81bba83b37382a2d0e30ceced06c8d3d85138054" + integrity sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q== + +"@rollup/rollup-darwin-arm64@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz#a371bd723a5c4c4a33376da72abfc3938066842b" + integrity sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA== + +"@rollup/rollup-darwin-x64@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz#8baf2fda277c9729125017c65651296282412886" + integrity sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz#822830a8f7388d5b81d04c69415408d3bab1079b" + integrity sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA== + +"@rollup/rollup-linux-arm64-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz#e20fbe1bd4414c7119f9e0bba8ad17a6666c8365" + integrity sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A== + +"@rollup/rollup-linux-arm64-musl@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz#13f475596a62e1924f13fe1c8cf2c40e09a99b47" + integrity sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz#6a431c441420d1c510a205e08c6673355a0a2ea9" + integrity sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA== + +"@rollup/rollup-linux-riscv64-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz#53d9448962c3f9ed7a1672269655476ea2d67567" + integrity sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw== + +"@rollup/rollup-linux-s390x-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz#95f0c133b324da3e7e5c7d12855e0eb71d21a946" + integrity sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA== + +"@rollup/rollup-linux-x64-gnu@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz#820ada75c68ead1acc486e41238ca0d8f8531478" + integrity sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg== + +"@rollup/rollup-linux-x64-musl@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz#ca74f22e125efbe94c1148d989ef93329b464443" + integrity sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg== + +"@rollup/rollup-win32-arm64-msvc@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz#269023332297051d037a9593dcba92c10fef726b" + integrity sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ== + +"@rollup/rollup-win32-ia32-msvc@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz#d7701438daf964011fd7ca33e3f13f3ff5129e7b" + integrity sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw== + +"@rollup/rollup-win32-x64-msvc@4.14.0": + version "4.14.0" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz#0bb7ac3cd1c3292db1f39afdabfd03ccea3a3d34" + integrity sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag== + "@rushstack/eslint-patch@^1.1.0": version "1.1.3" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz#6801033be7ff87a6b7cadaf5b337c9f366a3c4b0" integrity sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -2063,6 +2270,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@1.0.5", "@types/estree@^1.0.0": + version "1.0.5" + resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": version "4.17.28" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" @@ -2541,6 +2753,50 @@ "@typescript-eslint/types" "5.30.5" eslint-visitor-keys "^3.3.0" +"@vitest/expect@1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vitest/expect/-/expect-1.4.0.tgz#d64e17838a20007fecd252397f9b96a1ca81bfb0" + integrity sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA== + dependencies: + "@vitest/spy" "1.4.0" + "@vitest/utils" "1.4.0" + chai "^4.3.10" + +"@vitest/runner@1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vitest/runner/-/runner-1.4.0.tgz#907c2d17ad5975b70882c25ab7a13b73e5a28da9" + integrity sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg== + dependencies: + "@vitest/utils" "1.4.0" + p-limit "^5.0.0" + pathe "^1.1.1" + +"@vitest/snapshot@1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-1.4.0.tgz#2945b3fb53767a3f4f421919e93edfef2935b8bd" + integrity sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A== + dependencies: + magic-string "^0.30.5" + pathe "^1.1.1" + pretty-format "^29.7.0" + +"@vitest/spy@1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vitest/spy/-/spy-1.4.0.tgz#cf953c93ae54885e801cbe6b408a547ae613f26c" + integrity sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q== + dependencies: + tinyspy "^2.2.0" + +"@vitest/utils@1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vitest/utils/-/utils-1.4.0.tgz#ea6297e0d329f9ff0a106f4e1f6daf3ff6aad3f0" + integrity sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg== + dependencies: + diff-sequences "^29.6.3" + estree-walker "^3.0.3" + loupe "^2.3.7" + pretty-format "^29.7.0" + "@vue/component-compiler-utils@^2.2.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz#aa46d2a6f7647440b0b8932434d22f12371e543b" @@ -2791,11 +3047,21 @@ acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn-walk@^8.3.2: + version "8.3.2" + resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + acorn@^7.0.0, acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.11.3: + version "8.11.3" + resolved "https://registry.npmmirror.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" @@ -2843,6 +3109,21 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ahooks@^3.7.11: + version "3.7.11" + resolved "https://registry.npmmirror.com/ahooks/-/ahooks-3.7.11.tgz#51b6fdcad8cb603511318ac370feee0628bd6d68" + integrity sha512-BfSq7HJ9wk/7a2vX7WbLdwzHyQHmbNe21ipX1PfIzssXIzQfAl79WVJ9GjZaqNl4PFPsJusj/Xjg2OF+gIgGaQ== + dependencies: + "@babel/runtime" "^7.21.0" + dayjs "^1.9.1" + intersection-observer "^0.12.0" + js-cookie "^2.x.x" + lodash "^4.17.21" + react-fast-compare "^3.2.2" + resize-observer-polyfill "^1.5.1" + screenfull "^5.0.0" + tslib "^2.4.1" + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -3141,6 +3422,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" @@ -3511,6 +3797,11 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + cacache@^15.2.0: version "15.3.0" resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" @@ -3605,6 +3896,19 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chai@^4.3.10: + version "4.4.1" + resolved "https://registry.npmmirror.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" + integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + chalk@^2.0.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3642,6 +3946,13 @@ charenc@0.0.2: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + check-types@^11.1.1: version "11.1.2" resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f" @@ -4300,6 +4611,11 @@ dayjs@^1.11.1: resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== +dayjs@^1.9.1: + version "1.11.10" + resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + debug@2.6.9, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4344,6 +4660,13 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-eql@^4.1.3: + version "4.1.3" + resolved "https://registry.npmmirror.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" + integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== + dependencies: + type-detect "^4.0.0" + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4436,6 +4759,11 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -4974,6 +5302,35 @@ esbuild@^0.14.6: esbuild-windows-64 "0.14.36" esbuild-windows-arm64 "0.14.36" +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -5341,6 +5698,13 @@ estree-walker@^1.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -5391,6 +5755,21 @@ execa@^6.1.0: signal-exit "^3.0.7" strip-final-newline "^3.0.0" +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.npmmirror.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + exenv@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" @@ -5749,6 +6128,11 @@ fsevents@^2.3.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -5820,6 +6204,11 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" @@ -5849,6 +6238,11 @@ get-stream@^6.0.0, get-stream@^6.0.1: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -6277,6 +6671,11 @@ human-signals@^3.0.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-3.0.1.tgz#c740920859dafa50e5a3222da9d3bf4bb0e5eef5" integrity sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -6422,6 +6821,11 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +intersection-observer@^0.12.0: + version "0.12.2" + resolved "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375" + integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg== + inversify@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/inversify/-/inversify-6.0.1.tgz#b20d35425d5d8c5cd156120237aad0008d969f02" @@ -7161,11 +7565,21 @@ js-base64@^2.4.3: resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== +js-cookie@^2.x.x: + version "2.2.1" + resolved "https://registry.npmmirror.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-tokens@^9.0.0: + version "9.0.0" + resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.0.tgz#0f893996d6f3ed46df7f0a3b12a03f5fd84223c1" + integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ== + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -7283,6 +7697,11 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +jsonc-parser@^3.2.0: + version "3.2.1" + resolved "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -7434,6 +7853,14 @@ loader-utils@^3.2.0: resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== +local-pkg@^0.5.0: + version "0.5.0" + resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.0.tgz#093d25a346bae59a99f80e75f6e9d36d7e8c925c" + integrity sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg== + dependencies: + mlly "^1.4.2" + pkg-types "^1.0.3" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -7541,6 +7968,13 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +loupe@^2.3.6, loupe@^2.3.7: + version "2.3.7" + resolved "https://registry.npmmirror.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -7570,6 +8004,13 @@ magic-string@^0.25.0, magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.8" +magic-string@^0.30.5: + version "0.30.9" + resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d" + integrity sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -7867,6 +8308,35 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mlly@^1.2.0, mlly@^1.4.2: + version "1.6.1" + resolved "https://registry.npmmirror.com/mlly/-/mlly-1.6.1.tgz#0983067dc3366d6314fc5e12712884e6978d028f" + integrity sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA== + dependencies: + acorn "^8.11.3" + pathe "^1.1.2" + pkg-types "^1.0.3" + ufo "^1.3.2" + +mobx-react-lite@^4.0.7: + version "4.0.7" + resolved "https://registry.npmmirror.com/mobx-react-lite/-/mobx-react-lite-4.0.7.tgz#f4e21e18d05c811010dcb1d3007e797924c4d90b" + integrity sha512-RjwdseshK9Mg8On5tyJZHtGD+J78ZnCnRaxeQDSiciKVQDUbfZcXhmld0VMxAwvcTnPEHZySGGewm467Fcpreg== + dependencies: + use-sync-external-store "^1.2.0" + +mobx-react@^9.1.1: + version "9.1.1" + resolved "https://registry.npmmirror.com/mobx-react/-/mobx-react-9.1.1.tgz#b96e0d5d74a3d02fc62729fd344b2a3ad2a88aae" + integrity sha512-gVV7AdSrAAxqXOJ2bAbGa5TkPqvITSzaPiiEkzpW4rRsMhSec7C2NBCJYILADHKp2tzOAIETGRsIY0UaCV5aEw== + dependencies: + mobx-react-lite "^4.0.7" + +mobx@^6.12.3: + version "6.12.3" + resolved "https://registry.npmmirror.com/mobx/-/mobx-6.12.3.tgz#b6a0fde4268116be602d50bffb32f1b90a8fb077" + integrity sha512-c8NKkO4R2lShkSXZ2Ongj1ycjugjzFFo/UswHBnS62y07DMcTc9Rvo03/3nRyszIvwPNljlkd4S828zIBv/piw== + moo@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4" @@ -7905,6 +8375,11 @@ nanoid@^3.3.1: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -8292,6 +8767,13 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" +p-limit@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" + integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== + dependencies: + yocto-queue "^1.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -8443,6 +8925,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.0, pathe@^1.1.1, pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -8480,6 +8972,15 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-types@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" + integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== + dependencies: + jsonc-parser "^3.2.0" + mlly "^1.2.0" + pathe "^1.1.0" + pkg-up@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" @@ -9036,6 +9537,15 @@ postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.4, postcss@^8.4.7: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9090,6 +9600,15 @@ pretty-format@^27.0.0, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -9704,6 +10223,11 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== +react-fast-compare@^3.2.2: + version "3.2.2" + resolved "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + react-i18next@^11.18.3: version "11.18.3" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.3.tgz#50211810bcc9fdea2d70c8aefdfff5f1eb39a923" @@ -9717,7 +10241,7 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -"react-is@^16.12.0 || ^17.0.0 || ^18.0.0": +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== @@ -10215,6 +10739,30 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" +rollup@^4.13.0: + version "4.14.0" + resolved "https://registry.npmmirror.com/rollup/-/rollup-4.14.0.tgz#c3e2cd479f1b2358b65c1f810fa05b51603d7be8" + integrity sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.14.0" + "@rollup/rollup-android-arm64" "4.14.0" + "@rollup/rollup-darwin-arm64" "4.14.0" + "@rollup/rollup-darwin-x64" "4.14.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.14.0" + "@rollup/rollup-linux-arm64-gnu" "4.14.0" + "@rollup/rollup-linux-arm64-musl" "4.14.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.14.0" + "@rollup/rollup-linux-riscv64-gnu" "4.14.0" + "@rollup/rollup-linux-s390x-gnu" "4.14.0" + "@rollup/rollup-linux-x64-gnu" "4.14.0" + "@rollup/rollup-linux-x64-musl" "4.14.0" + "@rollup/rollup-win32-arm64-msvc" "4.14.0" + "@rollup/rollup-win32-ia32-msvc" "4.14.0" + "@rollup/rollup-win32-x64-msvc" "4.14.0" + fsevents "~2.3.2" + rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" @@ -10360,6 +10908,11 @@ scoped-css-loader@^1.0.0: qs "^6.5.2" schema-utils "^1.0.0" +screenfull@^5.0.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" + integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA== + scroll-into-view-if-needed@^3.0.3: version "3.0.10" resolved "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz#38fbfe770d490baff0fb2ba34ae3539f6ec44e13" @@ -10513,11 +11066,21 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -10600,6 +11163,11 @@ source-list-map@^2.0.0, source-list-map@^2.0.1: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + source-map-loader@^3.0.0, source-map-loader@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-3.0.1.tgz#9ae5edc7c2d42570934be4c95d1ccc6352eba52d" @@ -10739,6 +11307,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + stackframe@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.1.tgz#1033a3473ee67f08e2f2fc8eba6aef4f845124e1" @@ -10749,6 +11322,11 @@ stackframe@^1.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +std-env@^3.5.0: + version "3.7.0" + resolved "https://registry.npmmirror.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + stdout-stream@^1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" @@ -10936,6 +11514,13 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-literal@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.0.tgz#6d82ade5e2e74f5c7e8739b6c84692bd65f0bd2a" + integrity sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw== + dependencies: + js-tokens "^9.0.0" + style-loader@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" @@ -11167,11 +11752,26 @@ tiny-invariant@^1.0.6: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== +tinybench@^2.5.1: + version "2.6.0" + resolved "https://registry.npmmirror.com/tinybench/-/tinybench-2.6.0.tgz#1423284ee22de07c91b3752c048d2764714b341b" + integrity sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA== + tinycolor2@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== +tinypool@^0.8.2: + version "0.8.3" + resolved "https://registry.npmmirror.com/tinypool/-/tinypool-0.8.3.tgz#e17d0a5315a7d425f875b05f7af653c225492d39" + integrity sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw== + +tinyspy@^2.2.0: + version "2.2.1" + resolved "https://registry.npmmirror.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1" + integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -11352,6 +11952,11 @@ tslib@^2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.4.1: + version "2.6.2" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -11385,7 +11990,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@4.0.8: +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -11440,6 +12045,11 @@ typescript@4.8: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790" integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw== +ufo@^1.3.2: + version "1.5.3" + resolved "https://registry.npmmirror.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344" + integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -11546,6 +12156,11 @@ use-memo-one@^1.1.1: resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20" integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ== +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -11622,6 +12237,54 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite-node@1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/vite-node/-/vite-node-1.4.0.tgz#265529d60570ca695ceb69391f87f92847934ad8" + integrity sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw== + dependencies: + cac "^6.7.14" + debug "^4.3.4" + pathe "^1.1.1" + picocolors "^1.0.0" + vite "^5.0.0" + +vite@^5.0.0: + version "5.2.8" + resolved "https://registry.npmmirror.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa" + integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/vitest/-/vitest-1.4.0.tgz#f5c812aaf5023818b89b7fc667fa45327396fece" + integrity sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw== + dependencies: + "@vitest/expect" "1.4.0" + "@vitest/runner" "1.4.0" + "@vitest/snapshot" "1.4.0" + "@vitest/spy" "1.4.0" + "@vitest/utils" "1.4.0" + acorn-walk "^8.3.2" + chai "^4.3.10" + debug "^4.3.4" + execa "^8.0.1" + local-pkg "^0.5.0" + magic-string "^0.30.5" + pathe "^1.1.1" + picocolors "^1.0.0" + std-env "^3.5.0" + strip-literal "^2.0.0" + tinybench "^2.5.1" + tinypool "^0.8.2" + vite "^5.0.0" + vite-node "1.4.0" + why-is-node-running "^2.2.2" + void-elements@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" @@ -11900,6 +12563,14 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +why-is-node-running@^2.2.2: + version "2.2.2" + resolved "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.2.2.tgz#4185b2b4699117819e7154594271e7e344c9973e" + integrity sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" @@ -12214,3 +12885,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yocto-queue@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==