diff --git a/.changeset/README.md b/.changeset/README.md index e5b6d8d..08d2af9 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -1,8 +1,13 @@ # Changesets -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) +Hello and welcome! This folder has been +automatically generated by `@changesets/cli`, a +build tool that works with multi-package repos, or +single-package repos to help you version and +publish your code. You can find the full +documentation for it +[in our repository](https://github.com/changesets/changesets) -We have a quick list of common questions to get you started engaging with this project in +We have a quick list of common questions to get +you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.vscode/launch.json b/.vscode/launch.json index 92df705..accbaa2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,12 @@ "request": "launch", "name": "Debug Current Test File", "autoAttachChildProcesses": true, - "skipFiles": ["/**", "**/node_modules/**"], + "skipFiles": [ + "/**", + "**/node_modules/**", + "**/dist/**", + "**/.next/**" + ], "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", "args": ["run", "${relativeFile}"], "smartStep": true, diff --git a/.vscode/settings.json b/.vscode/settings.json index 0967ef4..97d3e94 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,6 @@ -{} +{ + "eslint.options": { + "overrideConfigFile": "./eslint.config.js" + }, + "eslint.experimental.useFlatConfig": true +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..930c9ac --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,26 @@ +async function getConfig() { + const { default: baseConfig } = await import( + "@coord/config/eslint.config.js" + ); + return [ + ...baseConfig, + { + settings: { + next: { + rootDir: ["packages/docs"], + }, + }, + }, + { + ignores: [ + "**/dist/**/*", + "**/node_modules/**/*", + "**/.next/**/*", + ], + }, + { + files: ["./**/*.tsx", "./**/*.ts"], + }, + ]; +} +module.exports = getConfig(); diff --git a/package.json b/package.json index 0078ada..b7acc40 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "turbo run build", "dev": "turbo run dev", - "lint": "turbo run lint", + "lint": "eslint .", "test": "turbo run test", "test:watch": "turbo run test:watch", "deploy": "turbo run deploy", @@ -18,6 +18,7 @@ }, "devDependencies": { "@changesets/cli": "^2.26.1", + "@coord/config": "workspace:*", "@turbo/gen": "^1.10.1", "next": "^13.4.4", "prettier": "^2.8.8", @@ -25,5 +26,8 @@ "turbo": "^1.10.1", "vitest": "^0.31.4" }, - "packageManager": "pnpm@7.15.0" + "packageManager": "pnpm@7.15.0", + "dependencies": { + "eslint": "^8.44.0" + } } diff --git a/packages/code-motion-react/package.json b/packages/code-motion-react/package.json new file mode 100644 index 0000000..b7cc74f --- /dev/null +++ b/packages/code-motion-react/package.json @@ -0,0 +1,54 @@ +{ + "name": "@coord/code-motion-react", + "version": "0.3.0", + "main": "dist/index.js", + "module": "dist/index.cjs", + "types": "dist/index.d.js", + "license": "MIT", + "type": "module", + "scripts": { + "build": "rollup --config rollup.config.js", + "dev": "pnpm run build --environment NODE_ENV=development --watch", + "lint": "tsc", + "test": "vitest run", + "test:watch": "vitest", + "clean": "rm -rf ./dist ./.turbo ./node_modules" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@coord/code-motion": "workspace:*", + "@coord/core": "workspace:*" + }, + "devDependencies": { + "@coord/config": "workspace:*", + "@rollup/plugin-commonjs": "^25.0.3", + "@rollup/plugin-typescript": "^11.1.2", + "lodash-es": "^4.17.21", + "postcss": "^8.4.26", + "postcss-preset-env": "^9.0.0", + "rollup": "^3.26.3", + "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-typescript2": "^0.35.0", + "ts-dedent": "^2.2.0", + "@rollup/plugin-node-resolve": "^15.1.0", + "@types/lodash": "^4.14.195", + "@types/lodash-es": "^4.17.8", + "@types/node": "20.2.5", + "@types/react": "^18.2.15", + "autoprefixer": "10.4.14", + "clsx": "^1.2.1", + "fp-ts": "^2.16.0", + "lightningcss": "^1.21.5", + "lodash": "^4.17.21", + "postcss-variables-prefixer": "^1.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rollup-plugin-dts": "^5.3.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } +} diff --git a/packages/code-motion-react/rollup.config.js b/packages/code-motion-react/rollup.config.js new file mode 100644 index 0000000..fb0e512 --- /dev/null +++ b/packages/code-motion-react/rollup.config.js @@ -0,0 +1,5 @@ +import { createConfig } from "@coord/config/rollup/create-config.js"; + +export default createConfig({ + cssPrefix: "cm", +}); diff --git a/packages/code-motion-react/src/components/CodeMotion.tsx b/packages/code-motion-react/src/components/CodeMotion.tsx new file mode 100644 index 0000000..bd5f14b --- /dev/null +++ b/packages/code-motion-react/src/components/CodeMotion.tsx @@ -0,0 +1,179 @@ +import { + CSSProperties, + ComponentProps, + useEffect, + useLayoutEffect, + useRef, + useState, +} from "react"; + +import cn from "clsx"; +import { + Language, + Theme, + Token, + vscodeDark, +} from "@coord/code-motion"; + +import { isNumber } from "lodash-es"; + +import { + EasingOptions, + PartialBy, + useStopwatch, +} from "@coord/core"; +import { + RenderingState, + initializeRenderingState, + updateRenderingState, +} from "@/utils"; +import { + TypewriterRenderer, + FadeEffectRenderer, +} from "./renderers"; + +type CodeMotionProps = { + tokens: Token[]; + code: string; + language?: Language; + theme?: Theme; + fromTheme?: Theme; + transitionTime?: number; + transitionDuration?: number; + transitionMode?: "fade" | "typewriter"; + easing?: EasingOptions; + measureDurationPer?: + | "full-transition" + | "change-rate"; + + displayBackground?: boolean; +} & ComponentProps<"pre">; + +const defaultsPerMode: Record< + CodeMotionProps["transitionMode"] & string, + Partial +> = { + fade: { + transitionDuration: 0.8, + measureDurationPer: "full-transition", + }, + typewriter: { + transitionDuration: 2, + measureDurationPer: "change-rate", + }, +}; + +export function CodeMotion({ + className, + tokens, + code, + language = "tsx", + theme = vscodeDark, + fromTheme = theme, + style, + transitionTime, + transitionMode = "fade", + transitionDuration, + measureDurationPer, + easing, + ...props +}: + | PartialBy + | PartialBy) { + const modeDefaults = + defaultsPerMode[transitionMode]; + transitionDuration ??= + modeDefaults.transitionDuration ?? 0.5; + + measureDurationPer ??= + modeDefaults.measureDurationPer ?? + "full-transition"; + + const isFirstRender = useRef(true); + const controlled = isNumber(transitionTime); + const [renderingState, setRenderingState] = + useState(() => + initializeRenderingState( + tokens ?? code ?? "", + language, + theme, + fromTheme + ) + ); + + let duration = transitionDuration; + if (measureDurationPer === "change-rate") { + duration *= renderingState.changes / 100; + } + + const [transitionTimeState, setTransitionTime] = + useState(1); + const { play } = useStopwatch( + setTransitionTime, + { + durationInSeconds: duration, + easing, + } + ); + + useLayoutEffect(() => { + if (isFirstRender.current) return; + setRenderingState((prev) => + updateRenderingState(prev, { + code, + tokens, + language, + theme, + }) + ); + setTransitionTime(0); + }, [tokens, code, theme, language]); + + useLayoutEffect(() => { + if (controlled || isFirstRender.current) + return; + if (duration === 0) { + setTransitionTime(1); + return; + } + + play(0); + }, [renderingState]); + useEffect(() => { + isFirstRender.current = false; + return () => { + isFirstRender.current = true; + }; + }, []); + return ( +
+      {transitionMode === "fade" && (
+        
+      )}
+      {transitionMode === "typewriter" && (
+        
+      )}
+    
+ ); +} diff --git a/packages/code-motion-react/src/components/index.ts b/packages/code-motion-react/src/components/index.ts new file mode 100644 index 0000000..bcbb979 --- /dev/null +++ b/packages/code-motion-react/src/components/index.ts @@ -0,0 +1 @@ +export * from "./CodeMotion"; diff --git a/packages/code-motion-react/src/components/renderers/FadeEffectRenderer/index.tsx b/packages/code-motion-react/src/components/renderers/FadeEffectRenderer/index.tsx new file mode 100644 index 0000000..a1a45d5 --- /dev/null +++ b/packages/code-motion-react/src/components/renderers/FadeEffectRenderer/index.tsx @@ -0,0 +1,316 @@ +import { + Token, + computeStyles, +} from "@coord/code-motion"; +import { TokenRenderer } from "../Token"; +import { + CSSProperties, + forwardRef, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; +import { + clamp, + lerp, + inlined, + remap, +} from "@coord/core"; +import { TransitionRendererProps } from "../types"; +import { RenderingState } from "@/utils"; + +type TokenPosition = { + past: { x: number; y: number }; + future: { x: number; y: number }; +}; +type PositionMap = Map; +export function FadeEffectRenderer({ + renderingState, + transitionTime, +}: TransitionRendererProps) { + const [elementMap] = useState( + new Map() + ); + const [ + expectedPositionMap, + setExpectedPositionMap, + ] = useState(null); + + const [containerSize, setContainerSize] = + useState<{ + past: { width: number; height: number }; + future: { width: number; height: number }; + } | null>(null); + const ref = useRef(null); + + useLayoutEffect(() => { + if (!ref.current) return; + const positionMap = new Map< + Token, + { + past: { x: number; y: number }; + future: { x: number; y: number }; + } + >(); + const { lineHeight } = getComputedStyle( + ref.current + ); + + const lineHeightPx = parseFloat(lineHeight); + + let x = 0; + let y = 0; + + let pastX = 0; + let pastY = 0; + + let pastWidth = 0; + + let futureWidth = 0; + + for (const token of renderingState.tokens) { + const el = elementMap.get(token); + if (!el) continue; + + positionMap.set(token, { + past: { + x: pastX, + y: pastY, + }, + future: { + x, + y, + }, + }); + + const { width } = + el.getBoundingClientRect(); + + if (token.type !== "insertion") { + pastWidth = Math.max( + pastWidth, + pastX + width + ); + + if (token.skipLines) { + pastY += lineHeightPx * token.skipLines; + pastX = 0; + } else { + pastX += width; + } + } + + if (token.type !== "deletion") { + futureWidth = Math.max( + futureWidth, + x + width + ); + if (token.skipLines) { + y += lineHeightPx * token.skipLines; + x = 0; + } else { + x += width; + } + } + } + setExpectedPositionMap(positionMap); + setContainerSize({ + past: { + width: pastWidth, + height: pastY + lineHeightPx, + }, + future: { + width: futureWidth, + height: y + lineHeightPx, + }, + }); + }, [ + renderingState.tokens, + elementMap, + ref.current, + ]); + const transformTime = clamp( + remap(0.2, 0.8, 0, 1, transitionTime), + 0, + 1 + ); + return ( + + {renderingState.tokens.map((token, i) => { + const expectedPosition = + expectedPositionMap?.get(token); + return ( + { + if (!el) return; + elementMap.set(token, el); + }} + key={i} + token={token} + renderingState={renderingState} + expectedPosition={expectedPosition} + time={transitionTime} + /> + ); + })} + + ); +} + +const SingleToken = forwardRef< + HTMLSpanElement, + { + token: Token; + expectedPosition?: TokenPosition; + renderingState: RenderingState; + + time: number; + } +>(function SingleToken( + { + token, + expectedPosition, + renderingState, + + time, + }, + ref +) { + const offset = useMemo(() => { + if (!expectedPosition) return undefined; + if (token.type !== "static") return undefined; + const offset = { + x: + expectedPosition.past.x - + expectedPosition.future.x, + y: + expectedPosition.past.y - + expectedPosition.future.y, + }; + if (offset.x === 0 && offset.y === 0) + return undefined; + return offset; + }, [expectedPosition, token, renderingState]); + const positionStyles = + useMemo(() => { + if (token.type !== "deletion") return {}; + if (!expectedPosition) + return { + position: "absolute", + left: 0, + top: 0, + }; + return { + position: "absolute", + left: expectedPosition.past.x, + top: expectedPosition.past.y, + }; + }, [expectedPosition]); + + const tokenStyles = useMemo(() => { + if (token.type === "deletion") + return computeStyles( + renderingState.fromTheme, + ["foreground", ...token.pastStyles] + ); + + return computeStyles(renderingState.theme, [ + "foreground", + ...token.styles, + ]); + }, [renderingState, token]); + + const pastStyles = useMemo(() => { + if (token.type !== "static") return undefined; + return computeStyles( + renderingState.fromTheme, + ["foreground", ...token.pastStyles] + ); + }, [renderingState]); + + const transformTime = clamp( + 1 - remap(0.2, 0.8, 0, 1, time), + 0, + 1 + ); + + const deletionTime = clamp( + remap(0, 0.2, 0, 1, time), + 0, + 1 + ); + const insertionTime = clamp( + remap(0.8, 1, 0, 1, time), + 0, + 1 + ); + + const composedStyles = { + ...tokenStyles, + ...positionStyles, + }; + if (offset && transformTime > 0) { + composedStyles.transform = `translate(${ + offset.x * transformTime + }px, ${offset.y * transformTime}px)`; + } + + if (pastStyles) { + if (pastStyles.color !== tokenStyles.color) + composedStyles.color = `mix-color(in srgb, ${ + pastStyles.color + }, ${tokenStyles.color} ${time * 100}%)`; + + if ( + pastStyles.opacity !== tokenStyles.opacity + ) + composedStyles.opacity = lerp( + Number(pastStyles.opacity ?? 1), + Number(tokenStyles.opacity ?? 1), + time + ); + } + + if (token.type === "deletion") { + composedStyles.opacity = lerp( + Number(tokenStyles.opacity ?? 1), + 0, + deletionTime + ); + } else if (token.type === "insertion") { + composedStyles.opacity = lerp( + 0, + Number(tokenStyles.opacity ?? 1), + insertionTime + ); + } + return ( + + ); +}); diff --git a/packages/code-motion-react/src/components/renderers/Token/index.tsx b/packages/code-motion-react/src/components/renderers/Token/index.tsx new file mode 100644 index 0000000..4484080 --- /dev/null +++ b/packages/code-motion-react/src/components/renderers/Token/index.tsx @@ -0,0 +1,45 @@ +import React, { ComponentProps } from "react"; +import { Token } from "@coord/code-motion"; +import cn from "clsx"; + +export type TokenRendererProps = { + token: Token; + renderNewLines?: boolean; +} & ComponentProps<"span">; + +function TokenRendererComponent( + { + token, + renderNewLines, + style = {}, + ...props + }: TokenRendererProps, + ref: React.Ref +) { + return ( + <> + + {" ".repeat(token.indent) + token.content} + + {renderNewLines && + new Array(token.skipLines) + .fill(null) + .map((_, i) =>
)} + + ); +} + +export const TokenRenderer = React.forwardRef( + TokenRendererComponent +); diff --git a/packages/code-motion-react/src/components/renderers/TypewriterEffectRenderer/index.tsx b/packages/code-motion-react/src/components/renderers/TypewriterEffectRenderer/index.tsx new file mode 100644 index 0000000..12c68c4 --- /dev/null +++ b/packages/code-motion-react/src/components/renderers/TypewriterEffectRenderer/index.tsx @@ -0,0 +1,261 @@ +import { + Token, + computeStyles, +} from "@coord/code-motion"; +import { TokenRenderer } from "../Token"; +import { memo, useMemo } from "react"; +import { + clamp, + inverseLerp, + makeId, +} from "@coord/core"; +import { TransitionRendererProps } from "../types"; +import { RenderingState } from "@/utils"; + +type Group = { + id: string; + tokens: Token[]; + size: number; + position: number; + animationPosition: number; +}; + +const SingleToken = memo( + function SingleToken({ + token, + group, + renderingState, + transitionTime, + groupIsActive, + }: { + token: Token; + group: Group; + renderingState: RenderingState; + transitionTime: number; + groupIsActive: boolean; + }) { + const tokenStyles = useMemo(() => { + if ( + token.type === "deletion" || + (token.type === "static" && + !groupIsActive && + transitionTime <= 0) + ) { + return computeStyles( + renderingState.fromTheme, + ["foreground", ...token.pastStyles] + ); + } + + return computeStyles(renderingState.theme, [ + "foreground", + ...token.styles, + ]); + }, [renderingState]); + + if (transitionTime < 1) { + const copy = { + ...token, + }; + if (token.type !== "static") { + const size = + token.content.length + token.skipLines; + const trimmedSize = Math.round( + size * transitionTime + ); + copy.content = token.content.slice( + 0, + trimmedSize + ); + copy.skipLines = + trimmedSize - copy.content.length; + + if (transitionTime === 0) { + copy.indent = 0; + } + + if ( + groupIsActive && + token.skipLines && + token === + group.tokens[group.tokens.length - 1] + ) { + copy.skipLines = clamp( + copy.skipLines, + 1, + token.skipLines + ); + } + token = copy; + } + } + + return ( + + ); + }, + (prev, next) => { + return ( + prev.transitionTime === + next.transitionTime && + prev.groupIsActive === next.groupIsActive && + prev.renderingState === next.renderingState + ); + } +); + +const TokenGroup = memo( + function TokenGroup({ + group, + renderingState, + groupTransitionTime, + }: { + group: Group; + renderingState: RenderingState; + groupTransitionTime: number; + }) { + if (group.tokens.length === 0) return null; + if (group.tokens[0].type === "deletion") + groupTransitionTime = + 1 - groupTransitionTime; + + let position = 0; + const groupIsActive = + groupTransitionTime > 0 && + groupTransitionTime < 1; + return ( + <> + {group.tokens.map((token, i) => { + const tokenPosition = position; + const tokenSize = + token.content.length + + token.skipLines; + position += tokenSize; + const tokenTime = globalToLocalTime( + group.size, + tokenPosition, + tokenSize, + groupTransitionTime + ); + return ( + + ); + })} + + ); + }, + (prev, next) => { + return ( + prev.groupTransitionTime === + next.groupTransitionTime && + prev.renderingState === next.renderingState + ); + } +); + +export const TypewriterRenderer = memo( + function TypewriterRenderer({ + renderingState, + transitionTime, + }: TransitionRendererProps) { + const { groups, animatedCharacters } = + useMemo(() => { + let position = 0; + let animationPosition = 0; + let currentGroup: Group = { + id: makeId("group-"), + tokens: [], + size: 0, + position: 0, + animationPosition: 0, + }; + const groups: Group[] = [currentGroup]; + + let prev: Token | null = null; + + for (const token of renderingState.tokens) { + if (prev && prev.type !== token.type) { + currentGroup = { + id: makeId("group-"), + tokens: [], + size: 0, + position, + animationPosition, + }; + groups.push(currentGroup); + } + currentGroup.tokens.push(token); + prev = token; + const tokenSize = + token.content.length + + token.skipLines; + currentGroup.size += tokenSize; + position += tokenSize; + + if (token.type !== "static") { + animationPosition += tokenSize; + } + } + + return { + groups, + animatedCharacters: animationPosition, + characters: position, + }; + }, [renderingState.tokens]); + + return ( + + {groups.map((group) => ( + = + animatedCharacters * + transitionTime + ? 1 + : 0 + : globalToLocalTime( + animatedCharacters, + group.animationPosition, + group.size, + transitionTime + ) + } + /> + ))} + + ); + } +); + +const globalToLocalTime = ( + globalSize: number, + start: number, + size: number, + time: number +) => { + return clamp( + inverseLerp( + start, + start + size, + globalSize * time + ), + 0, + 1 + ); +}; diff --git a/packages/code-motion-react/src/components/renderers/index.tsx b/packages/code-motion-react/src/components/renderers/index.tsx new file mode 100644 index 0000000..88eb695 --- /dev/null +++ b/packages/code-motion-react/src/components/renderers/index.tsx @@ -0,0 +1,3 @@ +export * from "./Token"; +export * from "./FadeEffectRenderer"; +export * from "./TypewriterEffectRenderer"; diff --git a/packages/code-motion-react/src/components/renderers/types.ts b/packages/code-motion-react/src/components/renderers/types.ts new file mode 100644 index 0000000..cedaec3 --- /dev/null +++ b/packages/code-motion-react/src/components/renderers/types.ts @@ -0,0 +1,6 @@ +import { RenderingState } from "@/utils"; + +export type TransitionRendererProps = { + renderingState: RenderingState; + transitionTime: number; +}; diff --git a/packages/code-motion-react/src/global.d.ts b/packages/code-motion-react/src/global.d.ts new file mode 100644 index 0000000..cbe652d --- /dev/null +++ b/packages/code-motion-react/src/global.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/packages/code-motion-react/src/index.ts b/packages/code-motion-react/src/index.ts new file mode 100644 index 0000000..5a2923e --- /dev/null +++ b/packages/code-motion-react/src/index.ts @@ -0,0 +1,2 @@ +export * from "./components/renderers"; +export * from "./components/CodeMotion"; diff --git a/packages/code-motion-react/src/utils/index.ts b/packages/code-motion-react/src/utils/index.ts new file mode 100644 index 0000000..bf2db03 --- /dev/null +++ b/packages/code-motion-react/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./rendering-state"; diff --git a/packages/code-motion-react/src/utils/rendering-state.tsx b/packages/code-motion-react/src/utils/rendering-state.tsx new file mode 100644 index 0000000..852f641 --- /dev/null +++ b/packages/code-motion-react/src/utils/rendering-state.tsx @@ -0,0 +1,179 @@ +import { + Language, + Theme, + Token, + diffTokenizer, + highlightTokens, + stringifyTokens, + tokenize, +} from "@coord/code-motion"; +import { pipe } from "fp-ts/function"; +import { isString } from "lodash"; +import dedent from "ts-dedent"; + +export type RenderingState = { + code: string; + tokens: Token[]; + language: Language; + theme: Theme; + fromTheme: Theme; + fromLanguage: Language; + changes: number; +}; + +const stringifyFuture = stringifyTokens("future"); + +const tokenizeCode = ( + code: string, + language: Language, + theme: Theme, + prevCode?: string, + prevLanguage?: Language, + prevTheme?: Theme +) => + pipe( + code, + (code) => + isString(prevCode) + ? diffTokenizer(prevCode, code) + : tokenize(code), + + highlightTokens(language, theme, "future"), + highlightTokens( + prevLanguage ?? language, + prevTheme ?? theme, + "past" + ) + ); + +export const initializeFromCode = ( + code: string, + language: Language, + theme: Theme, + fromTheme = theme +): RenderingState => { + return { + code, + changes: 0, + tokens: tokenizeCode(code, language, theme), + language, + theme, + fromTheme, + fromLanguage: language, + }; +}; + +const countChanges = (tokens: Token[]) => + tokens.reduce( + (acc, { content, skipLines, type }) => + type !== "static" + ? acc + content.length + skipLines + : acc, + 0 + ); +export const initializeFromTokens = ( + tokens: Token[], + language: Language, + theme: Theme, + fromTheme = theme +): RenderingState => { + const code = stringifyFuture(tokens); + + return { + code, + changes: countChanges(tokens), + tokens, + language, + theme, + fromTheme, + fromLanguage: language, + }; +}; + +export const initializeRenderingState = ( + codeOrTokens: string | Token[], + language: Language, + theme: Theme, + fromTheme = theme +): RenderingState => { + if (isString(codeOrTokens)) { + return initializeFromCode( + codeOrTokens, + language, + theme, + fromTheme + ); + } + return initializeFromTokens( + codeOrTokens, + language, + theme, + fromTheme + ); +}; +type ExtractEntries< + T extends { + [key: string]: unknown; + } +> = { + [K in keyof T]: [K, T[K]]; +}[keyof T] & + [string, unknown]; + +const entries = < + T extends { + [key: string]: unknown; + } +>( + obj: T +) => Object.entries(obj) as ExtractEntries[]; + +export const updateRenderingState = ( + state: RenderingState, + update: Partial +) => { + const newState = { + ...state, + ...update, + }; + const updateEntries = entries(update); + // ^? + for (const [key, value] of updateEntries) { + if (value === undefined) continue; + switch (key) { + case "code": { + // token updates have priority + if (update.tokens) continue; + const dedentedCode = dedent(value); + newState.tokens = tokenizeCode( + dedentedCode, + newState.language, + newState.theme, + state.code, + state.language, + state.theme + ); + newState.code = dedentedCode; + break; + } + case "tokens": { + const tokens = value; + newState.code = stringifyFuture(tokens); + newState.tokens = tokens; + break; + } + case "theme": { + newState.fromTheme = state.theme; + break; + } + case "language": { + newState.fromLanguage = state.language; + break; + } + } + } + newState.changes = countChanges( + newState.tokens + ); + return newState; +}; diff --git a/packages/code-motion-react/tsconfig.json b/packages/code-motion-react/tsconfig.json new file mode 100644 index 0000000..ab0315e --- /dev/null +++ b/packages/code-motion-react/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@coord/config/react-library.json", + "compilerOptions": { + "outDir": "dist", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "**/*.css"] +} diff --git a/packages/code-motion/eslint.config.js b/packages/code-motion/eslint.config.js new file mode 100644 index 0000000..2303a15 --- /dev/null +++ b/packages/code-motion/eslint.config.js @@ -0,0 +1,2 @@ +import custom from "@coord/config/eslint.config.js"; +export default custom; diff --git a/packages/code-motion/package.json b/packages/code-motion/package.json new file mode 100644 index 0000000..75b32cb --- /dev/null +++ b/packages/code-motion/package.json @@ -0,0 +1,45 @@ +{ + "name": "@coord/code-motion", + "version": "0.3.0", + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "license": "MIT", + "sideEffects": false, + "type": "module", + "scripts": { + "lint": "eslint .", + "build": "rollup --config rollup.config.js", + "dev": "pnpm run build --watch --environment NODE_ENV=development", + "test": "vitest run", + "test:watch": "vitest", + "test:inspect": "vitest --inspect-brk --single-thread", + "clean": "rm -rf ./dist ./.turbo ./node_modules" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": {}, + "devDependencies": { + "@coord/config": "workspace:*", + "@types/react": "^18.2.0", + "fast-diff": "^1.3.0", + "tsup": "^6.7.0", + "typescript": "^5.1.3", + "fp-ts": "^2.16.0", + "@codemirror/lang-javascript": "^6.1.9", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.2.1", + "@coord/core": "workspace:*", + "@lezer/common": "^1.0.3", + "@lezer/highlight": "^1.1.6", + "@lezer/javascript": "^1.4.4", + "@lezer/lr": "^1.3.6", + "@uiw/codemirror-themes": "^4.21.3", + "@uiw/codemirror-themes-all": "^4.21.3", + "clsx": "^1.2.1", + "rollup": "^3.26.3", + "ts-dedent": "^2.2.0", + "vitest": "^0.33.0" + } +} diff --git a/packages/code-motion/rollup.config.js b/packages/code-motion/rollup.config.js new file mode 100644 index 0000000..0e3500a --- /dev/null +++ b/packages/code-motion/rollup.config.js @@ -0,0 +1,3 @@ +import { createConfig } from "@coord/config/rollup/create-config.js"; + +export default createConfig(); diff --git a/packages/code-motion/src/index.ts b/packages/code-motion/src/index.ts new file mode 100644 index 0000000..9109d81 --- /dev/null +++ b/packages/code-motion/src/index.ts @@ -0,0 +1,5 @@ +export * from "./themes"; +export * from "./tokenizer"; +export * from "./theming"; +export * from "./language"; +export * from "./types"; diff --git a/packages/code-motion/src/language/highlight-tokens.ts b/packages/code-motion/src/language/highlight-tokens.ts new file mode 100644 index 0000000..be0548f --- /dev/null +++ b/packages/code-motion/src/language/highlight-tokens.ts @@ -0,0 +1,50 @@ +import { highlightTree } from "@lezer/highlight"; +import { Language, getParser } from "./parsers"; +import { Theme } from "@/theming"; +import { LimitedApplyTo, Token } from "@/types"; +import { Tokenizer } from "@/tokenizer"; + +export const highlightTokens = + ( + language: Language, + theme: Theme, + applyTo: LimitedApplyTo = "future" + ) => + (tokens: Token[]) => { + const parser = getParser(language); + if (!parser) return tokens; + + const tokenizer = + Tokenizer.fromTokens(tokens); + + const str = + applyTo === "future" + ? tokenizer.stringifyFuture() + : tokenizer.stringifyPast(); + + const mapUntil = + tokenizer.remapByPosition(applyTo); + const stylesKey = + applyTo === "future" + ? "styles" + : "pastStyles"; + highlightTree( + parser.parse(str), + theme.highlighter, + (from, to, id) => { + mapUntil(from, (token) => token); + mapUntil(to, (token) => { + return { + ...token, + [stylesKey]: [ + ...token[stylesKey], + ...id.split(" "), + ], + }; + }); + } + ); + mapUntil(Infinity, (token) => token); + + return tokenizer.tokens; + }; diff --git a/packages/code-motion/src/language/index.ts b/packages/code-motion/src/language/index.ts new file mode 100644 index 0000000..262bf1a --- /dev/null +++ b/packages/code-motion/src/language/index.ts @@ -0,0 +1,2 @@ +export * from "./parsers"; +export * from "./highlight-tokens"; diff --git a/packages/code-motion/src/language/parsers.ts b/packages/code-motion/src/language/parsers.ts new file mode 100644 index 0000000..a057b10 --- /dev/null +++ b/packages/code-motion/src/language/parsers.ts @@ -0,0 +1,32 @@ +import { isString } from "@coord/core"; +import { parser } from "@lezer/javascript"; +import type { LRParser } from "@lezer/lr"; +import { has } from "fp-ts/Record"; + +const parsers = { + javascript: parser, + typescript: parser.configure({ + dialect: "ts", + }), + jsx: parser.configure({ + dialect: "jsx", + }), + tsx: parser.configure({ + dialect: "jsx ts", + }), +}; + +export const getParser = (language: Language) => { + if (isString(language)) { + if (!has(language, parsers)) { + return null; + } + return parsers[language]; + } + return language; +}; + +export type Language = + | keyof typeof parsers + | (string & {}) + | LRParser; diff --git a/packages/code-motion/src/themes/abcdef.ts b/packages/code-motion/src/themes/abcdef.ts new file mode 100644 index 0000000..74c2b29 --- /dev/null +++ b/packages/code-motion/src/themes/abcdef.ts @@ -0,0 +1,50 @@ +/** + * @name abcdef + * @author codemirror.net + * https://codemirror.net/5/theme/abcdef.css + */ +import { makeTheme } from "@/theming"; + +export const abcdef = makeTheme( + "abcdef", + "dark", + { + background: { background: "#0f0f0f" }, + foreground: { color: "#defdef" }, + caret: { color: "#00FF00" }, + selection: { color: "#515151" }, + selectionMatch: { color: "#515151" }, + gutter: { + background: "#555", + color: "#FFFFFF", + }, + lineHighlight: { color: "#314151" }, + keyword: { + color: "darkgoldenrod", + fontWeight: "bold", + }, + atom: { color: "#77F" }, + comment: { + color: "#7a7b7c", + fontStyle: "italic", + }, + number: { color: "violet" }, + variableNameDefinition: { color: "#fffabc" }, + variableName: { color: "#abcdef" }, + variableNameFunction: { color: "#fffabc" }, + typeName: { color: "#FFDD44" }, + tagName: { color: "#def" }, + string: { color: "#2b4" }, + meta: { color: "#C9F" }, + bracket: { color: "#8a8a8a" }, + attributeName: { color: "#DDFF00" }, + heading: { + color: "aquamarine", + fontWeight: "bold", + }, + link: { + color: "blueviolet", + fontWeight: "bold", + }, + } +); diff --git a/packages/code-motion/src/themes/androidstudio.ts b/packages/code-motion/src/themes/androidstudio.ts new file mode 100644 index 0000000..091b139 --- /dev/null +++ b/packages/code-motion/src/themes/androidstudio.ts @@ -0,0 +1,29 @@ +import { makeTheme } from "@/theming"; + +export const androidstudio = makeTheme( + "androidstudio", + "dark", + { + background: { background: "#282b2e" }, + foreground: { color: "#a9b7c6" }, + caret: { color: "#00FF00" }, + selection: { color: "#343739" }, + selectionMatch: { color: "#343739" }, + lineHighlight: { color: "#343739" }, + "keyword,deleted,className": { + color: "#cc7832", + }, + "number,literal,derefOperator": { + color: "#6897bb", + }, + "link,variableName": { color: "#629755" }, + "comment,quote": { color: "grey" }, + "meta,documentMeta": { color: "#bbb529" }, + "string,propertyName,attributeValue": { + color: "#6a8759", + }, + "heading,typeName": { color: "#ffc66d" }, + attributeName: { color: "#a9b7c6" }, + emphasis: { fontStyle: "italic" }, + } +); diff --git a/packages/code-motion/src/themes/atomone.ts b/packages/code-motion/src/themes/atomone.ts new file mode 100644 index 0000000..3875026 --- /dev/null +++ b/packages/code-motion/src/themes/atomone.ts @@ -0,0 +1,38 @@ +/** + * @name Atom One + * Atom One dark syntax theme + * + * https://github.com/atom/one-dark-syntax + */ +import { makeTheme } from "@/theming"; + +export const atomone = makeTheme( + "atomone", + "dark", + { + background: { background: "#272C35" }, + foreground: { color: "#9d9b97" }, + caret: { color: "#797977" }, + selection: { color: "#ffffff30" }, + selectionMatch: { color: "#2B323D" }, + + gutter: { + background: "#272C35", + color: "#465063", + }, + lineHighlight: { color: "#2B323D" }, + "variableNameFunction,propertyNameFunction,url,processingInstruction": + { color: "hsl(207, 82%, 66%)" }, + "tagName,heading": { color: "#e06c75" }, + comment: { color: "#54636D" }, + propertyName: { color: "hsl(220, 14%, 71%)" }, + "attributeName,number": { + color: "hsl( 29, 54%, 61%)", + }, + className: { color: "hsl( 39, 67%, 69%)" }, + keyword: { color: "hsl(286, 60%, 67%)" }, + "string,regexp,propertyNameSpecial": { + color: "#98c379", + }, + } +); diff --git a/packages/code-motion/src/themes/aura.ts b/packages/code-motion/src/themes/aura.ts new file mode 100644 index 0000000..e00f87c --- /dev/null +++ b/packages/code-motion/src/themes/aura.ts @@ -0,0 +1,53 @@ +import { makeTheme } from "@/theming"; + +export const aura = makeTheme("aura", "dark", { + background: { background: "#21202e" }, + foreground: { color: "#edecee" }, + caret: { color: "#a277ff" }, + selection: { color: "#3d375e7f" }, + selectionMatch: { color: "#3d375e7f" }, + gutter: { + background: "#21202e", + color: "#edecee", + }, + lineHighlight: { color: "#a394f033" }, + keyword: { color: "#a277ff" }, + "name,deleted,character,macroName": { + color: "#edecee", + }, + propertyName: { color: "#ffca85" }, + "processingInstruction,string,inserted,stringSpecial": + { color: "#61ffca" }, + "variableNameFunction,labelName": { + color: "#ffca85", + }, + "color,nameConstant,nameStandard": { + color: "#61ffca", + }, + "nameDefinition,separator": { + color: "#edecee", + }, + className: { color: "#82e2ff" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#61ffca" }, + typeName: { color: "#82e2ff" }, + "operator,operatorKeyword": { + color: "#a277ff", + }, + "url,escape,regexp,link": { color: "#61ffca" }, + "meta,comment": { color: "#6d6d6d" }, + strong: { fontWeight: "bold" }, + emphasis: { fontStyle: "italic" }, + link: { textDecoration: "underline" }, + heading: { + fontWeight: "bold", + color: "#a277ff", + }, + "atom,bool,variableNameSpecial": { + color: "#edecee", + }, + invalid: { color: "#ff6767" }, + strikethrough: { + textDecoration: "line-through", + }, +}); diff --git a/packages/code-motion/src/themes/bbedit.ts b/packages/code-motion/src/themes/bbedit.ts new file mode 100644 index 0000000..b6f1379 --- /dev/null +++ b/packages/code-motion/src/themes/bbedit.ts @@ -0,0 +1,34 @@ +import { makeTheme } from "@/theming"; + +export const bbedit = makeTheme( + "bbedit", + "light", + { + background: { background: "#FFFFFF" }, + foreground: { color: "#000000" }, + caret: { color: "#FBAC52" }, + selection: { color: "#FFD420" }, + selectionMatch: { color: "#FFD420" }, + + gutter: { + background: "#f5f5f5", + color: "#4D4D4C", + }, + lineHighlight: { color: "#00000012" }, + "meta,comment": { color: "#804000" }, + "keyword,strong": { color: "#0000FF" }, + number: { color: "#FF0080" }, + string: { color: "#FF0080" }, + variableName: { color: "#006600" }, + escape: { color: "#33CC33" }, + tagName: { color: "#1C02FF" }, + heading: { color: "#0C07FF" }, + quote: { color: "#000000" }, + list: { color: "#B90690" }, + documentMeta: { color: "#888888" }, + variableNameFunction: { color: "#0000A2" }, + "typeNameDefinition,typeName": { + color: "#6D79DE", + }, + } +); diff --git a/packages/code-motion/src/themes/bespin.ts b/packages/code-motion/src/themes/bespin.ts new file mode 100644 index 0000000..cf6a4ca --- /dev/null +++ b/packages/code-motion/src/themes/bespin.ts @@ -0,0 +1,49 @@ +/** + * @name Bespin + * @author Mozilla / Jan T. Sott + * + * CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + * Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + */ + +import { makeTheme } from "@/theming"; + +export const bespin = makeTheme( + "bespin", + "dark", + { + background: { background: "#28211c" }, + foreground: { color: "#9d9b97" }, + caret: { color: "#797977" }, + selection: { color: "#36312e" }, + selectionMatch: { color: "#4f382b" }, + gutter: { + background: "#28211c", + color: "#666666", + }, + lineHighlight: { + color: "rgba(255, 255, 255, 0.1)", + }, + "atom,number,link,bool": { + color: "#9b859d", + }, + comment: { color: "#937121" }, + "keyword,tagName": { + color: "#cf6a4c", + }, + string: { color: "#f9ee98" }, + bracket: { color: "#9d9b97" }, + "variableName,propertyName,attributeName": { + color: "#5ea6ea", + }, + variableNameDefinition: { + color: "#cf7d34", + }, + "variableNameFunction,className": { + color: "#cf7d34", + }, + "propertyName,attributeName": { + color: "#54be0d", + }, + } +); diff --git a/packages/code-motion/src/themes/curves.ts b/packages/code-motion/src/themes/curves.ts new file mode 100644 index 0000000..26de094 --- /dev/null +++ b/packages/code-motion/src/themes/curves.ts @@ -0,0 +1,45 @@ +import { makeTheme } from "@/theming"; + +const motion = "hsl(164 100.00% 60%)"; +const graph = "hsl(337 100.00% 60%)"; +const graph2 = "hsl(337 50.00% 70%)"; +const editor = "hsl(200 100.00% 60%)"; + +export const curves = makeTheme( + "curves", + "dark", + { + background: { background: "#051014" }, + foreground: { color: "#FFFFFF" }, + caret: { color: motion }, + selection: { background: "#4C5964" }, + selectionMatch: { background: "#3A546E" }, + gutter: { + background: "#303841", + color: "#FFFFFF70", + }, + lineHighlight: { color: "#00000059" }, + + "meta,comment": { color: "#A2A9B5" }, + "attributeName,keyword": { color: graph2 }, + variableNameFunction: { color: editor }, + "string,stringSpecial,regexp,attributeValue": + { + color: "#99C592", + }, + operator: { color: "#f47954" }, + "tagName,modifier": { color: graph }, + "number,tagNameDefinition,className,variableNameDefinition": + { + color: motion, + }, + "atom,bool,variableNameSpecial": { + color: graph, + }, + variableName: { color: editor }, + "propertyName,typeName": { color: "#629ccd" }, + propertyNameDefinition: { + color: "#36b7b5", + }, + } +); diff --git a/packages/code-motion/src/themes/darcula.ts b/packages/code-motion/src/themes/darcula.ts new file mode 100644 index 0000000..92edbf9 --- /dev/null +++ b/packages/code-motion/src/themes/darcula.ts @@ -0,0 +1,40 @@ +/** + * @name darcula + * @author darcula + * Name: IntelliJ IDEA darcula theme + * From IntelliJ IDEA by JetBrains + */ + +import { makeTheme } from "@/theming"; + +export const darcula = makeTheme( + "darcula", + "dark", + { + background: { background: "#2B2B2B" }, + foreground: { color: "#f8f8f2" }, + caret: { color: "#FFFFFF" }, + selection: { + color: "rgba(255, 255, 255, 0.1)", + }, + selectionMatch: { + color: "rgba(255, 255, 255, 0.2)", + }, + gutter: { + background: "#2B2B2B", + color: "#999", + }, + lineHighlight: { + color: "rgba(255, 255, 255, 0.1)", + }, + "atom,number": { color: "#bd93f9" }, + comment: { color: "#61A151" }, + string: { color: "#6A8759" }, + "variableName,operator": { color: "#A9B7C6" }, + "meta,className": { color: "#A9B7C6" }, + propertyName: { color: "#FFC66D" }, + keyword: { color: "#CC7832" }, + tagName: { color: "#ff79c6" }, + typeName: { color: "#ffb86c" }, + } +); diff --git a/packages/code-motion/src/themes/dracula.ts b/packages/code-motion/src/themes/dracula.ts new file mode 100644 index 0000000..14c26df --- /dev/null +++ b/packages/code-motion/src/themes/dracula.ts @@ -0,0 +1,34 @@ +import { makeTheme } from "@/theming"; +export const dracula = makeTheme( + "dracula", + "dark", + { + background: { background: "#282a36" }, + foreground: { color: "#f8f8f2" }, + caret: { color: "#f8f8f0" }, + selection: { + color: "rgba(255, 255, 255, 0.1)", + }, + selectionMatch: { + color: "rgba(255, 255, 255, 0.2)", + }, + gutter: { + background: "#282a36", + color: "#6D8A88", + }, + lineHighlight: { + color: "rgba(255, 255, 255, 0.1)", + }, + comment: { color: "#6272a4" }, + string: { color: "#f1fa8c" }, + atom: { color: "#bd93f9" }, + meta: { color: "#f8f8f2" }, + "keyword,operator,tagName": { + color: "#ff79c6", + }, + propertyNameFunction: { color: "#66d9ef" }, + "variableNameDefinition,variableNameFunction,className,attributeName": + { color: "#50fa7b" }, + atomDefinition: { color: "#bd93f9" }, + } +); diff --git a/packages/code-motion/src/themes/duotone.ts b/packages/code-motion/src/themes/duotone.ts new file mode 100644 index 0000000..2b7fd12 --- /dev/null +++ b/packages/code-motion/src/themes/duotone.ts @@ -0,0 +1,59 @@ +/** + * @name duotone + * @author Bram de Haan + * by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) + */ +import { makeTheme } from "@/theming"; +export const duotoneLight = makeTheme( + "duotone", + "light", + { + background: { background: "#faf8f5" }, + foreground: { color: "#b29762" }, + caret: { color: "#93abdc" }, + selection: { color: "#e3dcce" }, + selectionMatch: { color: "#e3dcce" }, + gutter: { + background: "#faf8f5", + color: "#cdc4b1", + }, + lineHighlight: { color: "#EFEFEF" }, + "comment,bracket": { color: "#b6ad9a" }, + "atom,number,keyword,link,attributeName,quote": + { color: "#063289" }, + "emphasis,heading,tagName,propertyName,variableName": + { color: "#2d2006" }, + "typeName,url,string": { color: "#896724" }, + "operator,string": { color: "#1659df" }, + propertyName: { color: "#b29762" }, + "unit,punctuation": { color: "#063289" }, + } +); + +export const duotoneDark = makeTheme( + "duotone", + "dark", + { + background: { background: "#2a2734" }, + foreground: { color: "#6c6783" }, + caret: { color: "#ffad5c" }, + selection: { + color: "rgba(255, 255, 255, 0.1)", + }, + gutter: { + background: "#2a2734", + color: "#545167", + }, + lineHighlight: { color: "#36334280" }, + "comment,bracket": { color: "#6c6783" }, + "atom,number,keyword,link,attributeName,quote": + { color: "#ffcc99" }, + "emphasis,heading,tagName,propertyName,className,variableName": + { color: "#eeebff" }, + "typeName,url": { color: "#7a63ee" }, + operator: { color: "#ffad5c" }, + string: { color: "#ffb870" }, + propertyName: { color: "#9a86fd" }, + "unit,punctuation": { color: "#e09142" }, + } +); diff --git a/packages/code-motion/src/themes/eclipse.ts b/packages/code-motion/src/themes/eclipse.ts new file mode 100644 index 0000000..d746120 --- /dev/null +++ b/packages/code-motion/src/themes/eclipse.ts @@ -0,0 +1,34 @@ +import { makeTheme } from "@/theming"; + +export const eclipse = makeTheme( + "eclipse", + "light", + { + background: { background: "#fff" }, + foreground: { color: "#000" }, + caret: { color: "#FFFFFF" }, + selection: { color: "#d7d4f0" }, + selectionMatch: { color: "#d7d4f0" }, + gutter: { + background: "#f7f7f7", + color: "#999", + }, + lineHighlight: { color: "#e8f2ff" }, + comment: { color: "#3F7F5F" }, + documentMeta: { color: "#FF1717" }, + keyword: { + color: "#7F0055", + fontWeight: "bold", + }, + atom: { color: "#00f" }, + number: { color: "#164" }, + propertyName: { color: "#164" }, + variableNameDefinition: { color: "#0000C0" }, + variableNameFunction: { color: "#0000C0" }, + string: { color: "#2A00FF" }, + operator: { color: "black" }, + tagName: { color: "#170" }, + attributeName: { color: "#00c" }, + link: { color: "#219" }, + } +); diff --git a/packages/code-motion/src/themes/github.ts b/packages/code-motion/src/themes/github.ts new file mode 100644 index 0000000..a8438d9 --- /dev/null +++ b/packages/code-motion/src/themes/github.ts @@ -0,0 +1,107 @@ +/** + * @name github + */ + +import { makeTheme } from "@/theming"; +export const githubLight = makeTheme( + "github", + "light", + { + background: { background: "#fff" }, + foreground: { color: "#24292e" }, + selection: { color: "#BBDFFF" }, + selectionMatch: { color: "#BBDFFF" }, + + gutter: { + background: "#fff", + color: "#6e7781", + }, + "tagNameStandard,tagName": { + color: "#116329", + }, + "comment,bracket": { color: "#6a737d" }, + "className,propertyName": { + color: "#6f42c1", + }, + "variableName,attributeName,number,operator": + { color: "#005cc5" }, + "keyword,typeName,typeOperator,typeName": { + color: "#d73a49", + }, + "string,meta,regexp": { color: "#032f62" }, + "name,quote": { color: "#22863a" }, + heading: { + color: "#24292e", + fontWeight: "bold", + }, + emphasis: { + color: "#24292e", + fontStyle: "italic", + }, + deleted: { + color: "#b31d28", + backgroundColor: "ffeef0", + }, + "atom,bool,variableNameSpecial": { + color: "#e36209", + }, + "url,escape,regexp,link": { + color: "#032f62", + }, + link: { textDecoration: "underline" }, + strikethrough: { + textDecoration: "line-through", + }, + invalid: { color: "#cb2431" }, + } +); + +export const githubDark = makeTheme( + "github", + "dark", + { + background: { background: "#0d1117" }, + foreground: { color: "#c9d1d9" }, + caret: { color: "#c9d1d9" }, + selection: { color: "#003d73" }, + selectionMatch: { color: "#003d73" }, + lineHighlight: { color: "#36334280" }, + "tagNameStandard,tagName": { + color: "#7ee787", + }, + "comment,bracket": { color: "#8b949e" }, + "className,propertyName": { + color: "#d2a8ff", + }, + "variableName,attributeName,number,operator": + { color: "#79c0ff" }, + "keyword,typeName,typeOperator,className": { + color: "#ff7b72", + }, + "string,meta,regexp": { color: "#a5d6ff" }, + "name,quote": { color: "#7ee787" }, + heading: { + color: "#d2a8ff", + fontWeight: "bold", + }, + emphasis: { + color: "#d2a8ff", + fontStyle: "italic", + }, + deleted: { + color: "#ffdcd7", + backgroundColor: "ffeef0", + }, + "atom,bool,variableNameSpecial": { + color: "#ffab70", + }, + "url,escape,regexp,link": { + color: "#032f62", + }, + link: { textDecoration: "underline" }, + strikethrough: { + textDecoration: "line-through", + }, + invalid: { color: "#f97583" }, + } +); diff --git a/packages/code-motion/src/themes/gruvbox.ts b/packages/code-motion/src/themes/gruvbox.ts new file mode 100644 index 0000000..8a6ec60 --- /dev/null +++ b/packages/code-motion/src/themes/gruvbox.ts @@ -0,0 +1,197 @@ +/** + * @name gruvbox-dark + * @author morhetz + * Name: Gruvbox + * From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-dark.css + */ + +import { makeTheme } from "@/theming"; +export const gruvboxDark = makeTheme( + "gruvbox", + "dark", + { + background: { background: "#282828" }, + foreground: { color: "#ebdbb2" }, + caret: { color: "#ebdbb2" }, + selection: { color: "#bdae93" }, + selectionMatch: { color: "#bdae93" }, + lineHighlight: { color: "#3c3836" }, + gutter: { + background: "#282828", + color: "#7c6f64", + }, + keyword: { color: "#fb4934" }, + "name,deleted,character,propertyName,macroName": + { color: "#8ec07c" }, + variableName: { color: "#83a598" }, + variableNameFunction: { + color: "#b8bb26", + fontStyle: "bold", + }, + labelName: { color: "#ebdbb2" }, + "color,nameConstant,nameStandard": { + color: "#d3869b", + }, + "nameDefinition,separator": { + color: "#ebdbb2", + }, + brace: { color: "#ebdbb2" }, + annotation: { color: "#fb4934d" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#d3869b" }, + "typeName,className": { color: "#fabd2f" }, + "operator,operatorKeyword": { + color: "#fb4934", + }, + tagName: { + color: "#8ec07c", + fontStyle: "bold", + }, + squareBracket: { color: "#fe8019" }, + angleBracket: { color: "#83a598" }, + attributeName: { color: "#8ec07c" }, + regexp: { color: "#8ec07c" }, + quote: { color: "#928374" }, + string: { color: "#ebdbb2" }, + link: { + color: "#a89984", + textDecoration: "underline", + textUnderlinePosition: "under", + }, + "url,escape,stringSpecial": { + color: "#d3869b", + }, + meta: { color: "#fabd2f" }, + comment: { + color: "#928374", + fontStyle: "italic", + }, + strong: { + fontWeight: "bold", + color: "#fe8019", + }, + emphasis: { + fontStyle: "italic", + color: "#b8bb26", + }, + strikethrough: { + textDecoration: "line-through", + }, + heading: { + fontWeight: "bold", + color: "#b8bb26", + }, + "heading1,heading2": { + fontWeight: "bold", + color: "#b8bb26", + }, + "heading3,heading4": { + fontWeight: "bold", + color: "#fabd2f", + }, + "heading5,heading6": { color: "#fabd2f" }, + "atom,bool,variableNameSpecial": { + color: "#d3869b", + }, + "processingInstruction,inserted": { + color: "#83a598", + }, + contentSeparator: { color: "#fb4934" }, + invalid: { color: "#fe8019" }, + } +); + +export const gruvboxLight = makeTheme( + "gruvbox", + "light", + { + background: { background: "#fbf1c7" }, + foreground: { color: "#3c3836" }, + caret: { color: "#af3a03" }, + selection: { color: "#ebdbb2" }, + selectionMatch: { color: "#bdae93" }, + lineHighlight: { color: "#ebdbb2" }, + gutter: { + background: "#ebdbb2", + color: "#665c54", + }, + keyword: { color: "#9d0006" }, + "name,deleted,character,propertyName,macroName": + { color: "#427b58" }, + variableName: { color: "#076678" }, + variableNameFunction: { + color: "#79740e", + fontStyle: "bold", + }, + labelName: { color: "#3c3836" }, + "color,nameConstant,nameStandard": { + color: "#8f3f71", + }, + "nameDefinition,separator": { + color: "#3c3836", + }, + brace: { color: "#3c3836" }, + annotation: { color: "#9d0006" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#8f3f71" }, + "typeName,className": { color: "#b57614" }, + "operator,operatorKeyword": { + color: "#9d0006", + }, + tagName: { + color: "#427b58", + fontStyle: "bold", + }, + squareBracket: { color: "#af3a03" }, + angleBracket: { color: "#076678" }, + attributeName: { color: "#427b58" }, + regexp: { color: "#427b58" }, + quote: { color: "#928374" }, + string: { color: "#3c3836" }, + link: { + color: "#7c6f64", + textDecoration: "underline", + textUnderlinePosition: "under", + }, + "url,escape,stringSpecial": { + color: "#8f3f71", + }, + meta: { color: "#b57614" }, + comment: { + color: "#928374", + fontStyle: "italic", + }, + strong: { + fontWeight: "bold", + color: "#af3a03", + }, + emphasis: { + fontStyle: "italic", + color: "#79740e", + }, + strikethrough: { + textDecoration: "line-through", + }, + heading: { + fontWeight: "bold", + color: "#79740e", + }, + "heading1,heading2": { + fontWeight: "bold", + color: "#79740e", + }, + "heading3,heading4": { + fontWeight: "bold", + color: "#b57614", + }, + "heading5,heading6": { color: "#b57614" }, + "atom,bool,variableNameSpecial": { + color: "#8f3f71", + }, + "processingInstruction,inserted": { + color: "#076678", + }, + contentSeparator: { color: "#9d0006" }, + invalid: { color: "#af3a03" }, + } +); diff --git a/packages/code-motion/src/themes/index.ts b/packages/code-motion/src/themes/index.ts new file mode 100644 index 0000000..a8afcaf --- /dev/null +++ b/packages/code-motion/src/themes/index.ts @@ -0,0 +1,24 @@ +export * from "./abcdef"; +export * from "./androidstudio"; +export * from "./atomone"; +export * from "./aura"; +export * from "./bbedit"; +export * from "./bespin"; +export * from "./darcula"; +export * from "./dracula"; +export * from "./duotone"; +export * from "./eclipse"; +export * from "./github"; +export * from "./gruvbox"; +export * from "./material"; +export * from "./noctisLilac"; +export * from "./nord"; +export * from "./okaidia"; +export * from "./solarized"; +export * from "./sublime"; +export * from "./tokyoNight"; +export * from "./tokyoNightDay"; +export * from "./tokyoNightStorm"; +export * from "./vscode"; +export * from "./xcode"; +export * from "./curves"; diff --git a/packages/code-motion/src/themes/material.ts b/packages/code-motion/src/themes/material.ts new file mode 100644 index 0000000..41c0308 --- /dev/null +++ b/packages/code-motion/src/themes/material.ts @@ -0,0 +1,120 @@ +import { makeTheme } from "@/theming"; + +export const materialDark = makeTheme( + "material", + "dark", + { + background: { background: "#2e3235" }, + foreground: { color: "#bdbdbd" }, + caret: { color: "#a0a4ae" }, + selection: { color: "#d7d4f0" }, + selectionMatch: { color: "#d7d4f0" }, + gutterActive: { + color: "#4f5b66", + }, + gutter: { + background: "#2e3235", + color: "#999", + }, + lineHighlight: { color: "#545b61" }, + keyword: { color: "#cf6edf" }, + "name,deleted,character,macroName": { + color: "#56c8d8", + }, + propertyName: { color: "#facf4e" }, + variableName: { color: "#bdbdbd" }, + variableNameFunction: { color: "#56c8d8" }, + labelName: { color: "#cf6edf" }, + "color,nameConstant,nameStandard": { + color: "#facf4e", + }, + "nameDefinition,separator": { + color: "#fa5788", + }, + brace: { color: "#cf6edf" }, + annotation: { color: "#ff5f52" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#ffad42" }, + "typeName,className": { color: "#ffad42" }, + "operator,operatorKeyword": { + color: "#7186f0", + }, + tagName: { color: "#99d066" }, + squareBracket: { color: "#ff5f52" }, + angleBracket: { color: "#606f7a" }, + attributeName: { color: "#bdbdbd" }, + regexp: { color: "#ff5f52" }, + quote: { color: "#6abf69" }, + string: { color: "#99d066" }, + "url,escape,stringSpecial": { + color: "#facf4e", + }, + meta: { color: "#707d8b" }, + "atom,bool,variableNameSpecial": { + color: "#56c8d8", + }, + "processingInstruction,inserted": { + color: "#ff5f52", + }, + contentSeparator: { color: "#56c8d8" }, + invalid: { + color: "#606f7a", + borderBottom: `1px dotted #ff5f52`, + }, + } +); + +export const materialLight = makeTheme( + "material", + "light", + { + background: { background: "#FAFAFA" }, + foreground: { color: "#90A4AE" }, + caret: { color: "#272727" }, + selection: { color: "#80CBC440" }, + selectionMatch: { color: "#FAFAFA" }, + gutter: { + background: "#FAFAFA", + color: "#90A4AE", + }, + lineHighlight: { color: "#CCD7DA50" }, + keyword: { color: "#39ADB5" }, + "name,deleted,character,macroName": { + color: "#90A4AE", + }, + propertyName: { color: "#6182B8" }, + "processingInstruction,string,inserted,stringSpecial": + { color: "#91B859" }, + "variableNameFunction,labelName": { + color: "#6182B8", + }, + "color,nameConstant,nameStandard": { + color: "#39ADB5", + }, + "nameDefinition,separator": { + color: "#90A4AE", + }, + className: { color: "#E2931D" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#F76D47" }, + typeName: { color: "#E2931D" }, + "operator,operatorKeyword": { + color: "#39ADB5", + }, + "url,escape,regexp,link": { + color: "#91B859", + }, + "meta,comment": { color: "#90A4AE" }, + strong: { fontWeight: "bold" }, + emphasis: { fontStyle: "italic" }, + link: { textDecoration: "underline" }, + heading: { + fontWeight: "bold", + color: "#39ADB5", + }, + "atom,bool,variableNameSpecial": { + color: "#90A4AE", + }, + invalid: { color: "#E5393570" }, + } +); diff --git a/packages/code-motion/src/themes/noctisLilac.ts b/packages/code-motion/src/themes/noctisLilac.ts new file mode 100644 index 0000000..73f6658 --- /dev/null +++ b/packages/code-motion/src/themes/noctisLilac.ts @@ -0,0 +1,45 @@ +import { makeTheme } from "@/theming"; + +export const noctisLilac = makeTheme( + "noctisLilac", + "light", + { + background: { background: "#f2f1f8" }, + foreground: { color: "#0c006b" }, + caret: { color: "#5c49e9" }, + selection: { color: "#d5d1f2" }, + selectionMatch: { color: "#d5d1f2" }, + gutter: { + background: "#f2f1f8", + color: "#0c006b70", + }, + lineHighlight: { color: "#e1def3" }, + comment: { color: "#9995b7" }, + keyword: { + color: "#ff5792", + fontWeight: "bold", + }, + "keywordDefinition,modifier": { + color: "#ff5792", + }, + "className,tagName,typeNameDefinition": { + color: "#0094f0", + }, + "number,bool,null,braceSpecial": { + color: "#5842ff", + }, + "propertyNameDefinition,variableNameFunction": + { color: "#0095a8" }, + typeName: { color: "#b3694d" }, + "propertyName,variableName": { + color: "#fa8900", + }, + operator: { color: "#ff5792" }, + self: { color: "#e64100" }, + "string,regexp": { color: "#00b368" }, + "paren,bracket": { color: "#0431fa" }, + labelName: { color: "#00bdd6" }, + attributeName: { color: "#e64100" }, + angleBracket: { color: "#9995b7" }, + } +); diff --git a/packages/code-motion/src/themes/nord.ts b/packages/code-motion/src/themes/nord.ts new file mode 100644 index 0000000..4664d6a --- /dev/null +++ b/packages/code-motion/src/themes/nord.ts @@ -0,0 +1,93 @@ +import { makeTheme } from "@/theming"; + +// Colors from https://www.nordtheme.com/docs/colors-and-palettes +export const nord = makeTheme("nord", "dark", { + background: { background: "#2e3440" }, + foreground: { color: "#FFFFFF" }, + caret: { color: "#FBAC52" }, + selection: { color: "#3b4252" }, + selectionMatch: { color: "#e5e9f0" }, + gutter: { + background: "#2e3440", + color: "#4c566a", + }, + gutterActive: { color: "#d8dee9" }, + lineHighlight: { color: "#4c566a" }, + keyword: { color: "#5e81ac" }, + "name,deleted,character,propertyName,macroName": + { color: "#88c0d0" }, + variableName: { color: "#8fbcbb" }, + variableNameFunction: { color: "#8fbcbb" }, + labelName: { color: "#81a1c1" }, + "color,nameConstant,nameStandardd": { + color: "#5e81ac", + }, + "nameDefinition,separator": { + color: "#a3be8c", + }, + brace: { color: "#8fbcbb" }, + annotation: { color: "#d30102" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#b48ead" }, + "typeName,className": { color: "#ebcb8b" }, + operator: { color: "#a3be8c" }, + tagName: { color: "#b48ead" }, + squareBracket: { color: "#bf616a" }, + angleBracket: { color: "#d08770" }, + attributeName: { color: "#ebcb8b" }, + regexp: { color: "#5e81ac" }, + quote: { color: "#b48ead" }, + string: { color: "#a3be8c" }, + link: { + color: "#a3be8c", + textDecoration: "underline", + textUnderlinePosition: "under", + }, + "url,escape,stringSpecial": { + color: "#8fbcbb", + }, + meta: { color: "#88c0d0" }, + monospace: { + color: "#d8dee9", + fontStyle: "italic", + }, + comment: { + color: "#4c566a", + fontStyle: "italic", + }, + strong: { + fontWeight: "bold", + color: "#5e81ac", + }, + emphasis: { + fontStyle: "italic", + color: "#5e81ac", + }, + strikethrough: { + textDecoration: "line-through", + }, + heading: { + fontWeight: "bold", + color: "#5e81ac", + }, + heading1: { + fontWeight: "bold", + color: "#5e81ac", + }, + "heading2,heading3,heading4": { + fontWeight: "bold", + color: "#5e81ac", + }, + "heading5,heading6": { color: "#5e81ac" }, + "atom,bool,variableNameSpecial": { + color: "#d08770", + }, + "processingInstruction,inserted": { + color: "#8fbcbb", + }, + contentSeparator: { color: "#ebcb8b" }, + invalid: { + color: "#434c5e", + borderBottom: `1px dotted #d30102`, + }, +}); diff --git a/packages/code-motion/src/themes/okaidia.ts b/packages/code-motion/src/themes/okaidia.ts new file mode 100644 index 0000000..f33da72 --- /dev/null +++ b/packages/code-motion/src/themes/okaidia.ts @@ -0,0 +1,39 @@ +import { makeTheme } from "@/theming"; + +export const okaidia = makeTheme( + "okaidia", + "dark", + { + background: { background: "#272822" }, + foreground: { color: "#FFFFFF" }, + caret: { color: "#FFFFFF" }, + selection: { color: "#49483E" }, + selectionMatch: { color: "#49483E" }, + gutter: { + background: "#272822", + color: "#FFFFFF70", + }, + lineHighlight: { color: "#00000059" }, + "comment,documentMeta": { color: "#8292a2" }, + "number,bool,null,atom": { color: "#ae81ff" }, + "attributeValue,className,name": { + color: "#e6db74", + }, + "propertyName,attributeName": { + color: "#a6e22e", + }, + variableName: { color: "#9effff" }, + squareBracket: { color: "#bababa" }, + "string,braceSpecial": { color: "#e6db74" }, + "regexp,className,typeName,typeNameDefinition": + { color: "#66d9ef" }, + "variableNameDefinition,propertyNameDefinition,variableNameFunction": + { + color: "#fd971f", + }, + "keyword,keywordDefinition,modifier,tagName,angleBracket": + { + color: "#f92672", + }, + } +); diff --git a/packages/code-motion/src/themes/solarized.ts b/packages/code-motion/src/themes/solarized.ts new file mode 100644 index 0000000..6197db2 --- /dev/null +++ b/packages/code-motion/src/themes/solarized.ts @@ -0,0 +1,188 @@ +import { makeTheme } from "@/theming"; +export const solarizedLight = makeTheme( + "solarized", + "light", + { + background: { background: "#fdf6e3" }, + foreground: { color: "#657b83" }, + caret: { color: "#586e75" }, + selection: { color: "#dfd9c8" }, + selectionMatch: { color: "#dfd9c8" }, + gutter: { + background: "#00000010", + color: "#657b83", + }, + lineHighlight: { color: "#dfd9c8" }, + keyword: { color: "#859900" }, + "name,deleted,character,propertyName,macroName": + { color: "#2aa198" }, + variableName: { color: "#268bd2" }, + variableNameFunction: { color: "#268bd2" }, + labelName: { color: "#d33682" }, + "color,nameConstant,nameStandard": { + color: "#b58900", + }, + "nameDefinition,separator": { + color: "#2aa198", + }, + brace: { color: "#d33682" }, + annotation: { color: "#d30102" }, + "number,changed,annotation,modifier,self,namespace": + { + color: "#d33682", + }, + "typeName,className": { color: "#cb4b16" }, + operator: { color: "#6c71c4" }, + tagName: { color: "#268bd2" }, + squareBracket: { color: "#dc322f" }, + angleBracket: { color: "#073642" }, + attributeName: { color: "#93a1a1" }, + regexp: { color: "#d30102" }, + quote: { color: "#859900" }, + string: { color: "#b58900" }, + link: { + color: "#2aa198", + textDecoration: "underline", + textUnderlinePosition: "under", + }, + "url,escape,stringSpecial": { + color: "#b58900", + }, + meta: { color: "#dc322f" }, + comment: { + color: "#586e75", + fontStyle: "italic", + }, + strong: { + fontWeight: "bold", + color: "#586e75", + }, + emphasis: { + fontStyle: "italic", + color: "#859900", + }, + strikethrough: { + textDecoration: "line-through", + }, + heading: { + fontWeight: "bold", + color: "#b58900", + }, + heading1: { + fontWeight: "bold", + color: "#002b36", + }, + "heading2,heading3,heading4": { + fontWeight: "bold", + color: "#002b36", + }, + "heading5,heading6": { + color: "#002b36", + }, + "atom,bool,variableNameSpecial": { + color: "#d33682", + }, + "processingInstruction,inserted,contentSeparator": + { + color: "#dc322f", + }, + invalid: { + color: "#073642", + borderBottom: `1px dotted #dc322f`, + }, + } +); + +export const solarizedDark = makeTheme( + "solarized", + "dark", + { + background: { background: "#002b36" }, + foreground: { color: "#93a1a1" }, + caret: { color: "#839496" }, + selection: { color: "#173541" }, + selectionMatch: { color: "#aafe661a" }, + gutter: { + background: "#00252f", + color: "#839496", + }, + lineHighlight: { color: "#173541" }, + keyword: { color: "#859900" }, + "name,deleted,character,propertyName,macroName": + { color: "#2aa198" }, + variableName: { color: "#93a1a1" }, + variableNameFunction: { color: "#268bd2" }, + labelName: { color: "#d33682" }, + "color,nameConstant,nameStandard": { + color: "#b58900", + }, + "nameDefinition,separator": { + color: "#2aa198", + }, + brace: { color: "#d33682" }, + annotation: { color: "#d30102" }, + "number,changed,annotation,modifier,self,namespace": + { + color: "#d33682", + }, + "typeName,className": { color: "#cb4b16" }, + operator: { color: "#6c71c4" }, + tagName: { color: "#268bd2" }, + squareBracket: { color: "#dc322f" }, + angleBracket: { color: "#586e75" }, + attributeName: { color: "#93a1a1" }, + regexp: { color: "#d30102" }, + quote: { color: "#859900" }, + string: { color: "#b58900" }, + link: { + color: "#2aa198", + textDecoration: "underline", + textUnderlinePosition: "under", + }, + "url,escape,stringSpecial": { + color: "#b58900", + }, + meta: { color: "#dc322f" }, + comment: { + color: "#586e75", + fontStyle: "italic", + }, + strong: { + fontWeight: "bold", + color: "#eee8d5", + }, + emphasis: { + fontStyle: "italic", + color: "#859900", + }, + strikethrough: { + textDecoration: "line-through", + }, + heading: { + fontWeight: "bold", + color: "#b58900", + }, + heading1: { + fontWeight: "bold", + color: "#fdf6e3", + }, + "heading2,heading3,heading4": { + fontWeight: "bold", + color: "#eee8d5", + }, + "heading5,heading6": { + color: "#eee8d5", + }, + "atom,bool,variableNameSpecial": { + color: "#d33682", + }, + "processingInstruction,inserted,contentSeparator": + { + color: "#dc322f", + }, + invalid: { + color: "#586e75", + borderBottom: `1px dotted #dc322f`, + }, + } +); diff --git a/packages/code-motion/src/themes/sublime.ts b/packages/code-motion/src/themes/sublime.ts new file mode 100644 index 0000000..bb0f4fc --- /dev/null +++ b/packages/code-motion/src/themes/sublime.ts @@ -0,0 +1,39 @@ +import { makeTheme } from "@/theming"; + +export const sublime = makeTheme( + "sublime", + "dark", + { + background: { background: "#303841" }, + foreground: { color: "#FFFFFF" }, + caret: { color: "#FBAC52" }, + selection: { color: "#4C5964" }, + selectionMatch: { color: "#3A546E" }, + gutter: { + background: "#303841", + color: "#FFFFFF70", + }, + lineHighlight: { color: "#00000059" }, + "meta,comment": { color: "#A2A9B5" }, + "attributeName,keyword": { color: "#B78FBA" }, + variableNameFunction: { color: "#5AB0B0" }, + "string,stringSpecial,regexp,attributeValue": + { + color: "#99C592", + }, + operator: { color: "#f47954" }, + "tagName,modifier": { color: "#E35F63" }, + "number,tagNameDefinition,className,variableNameDefinition": + { + color: "#fbac52", + }, + "atom,bool,variableNameSpecial": { + color: "#E35F63", + }, + variableName: { color: "#539ac4" }, + "propertyName,typeName": { color: "#629ccd" }, + propertyNameDefinition: { + color: "#36b7b5", + }, + } +); diff --git a/packages/code-motion/src/themes/tokyoNight.ts b/packages/code-motion/src/themes/tokyoNight.ts new file mode 100644 index 0000000..9654481 --- /dev/null +++ b/packages/code-motion/src/themes/tokyoNight.ts @@ -0,0 +1,56 @@ +import { makeTheme } from "@/theming"; + +export const tokyoNight = makeTheme( + "tokyoNight", + "dark", + { + background: { background: "#1a1b26" }, + foreground: { color: "#787c99" }, + caret: { color: "#c0caf5" }, + selection: { color: "#515c7e40" }, + selectionMatch: { color: "#16161e" }, + gutter: { + background: "#1a1b26", + color: "#787c99", + }, + lineHighlight: { color: "#1e202e" }, + keyword: { color: "#bb9af7" }, + "name,deleted,character,macroName": { + color: "#c0caf5", + }, + propertyName: { color: "#7aa2f7" }, + "processingInstruction,string,inserted,stringSpecial": + { color: "#9ece6a" }, + variableNameFunction: { color: "#7aa2f7" }, + labelName: { color: "#7aa2f7" }, + "color,nameConstant,nameStandard": { + color: "#bb9af7", + }, + "nameDefinition,separator": { + color: "#c0caf5", + }, + className: { color: "#c0caf5" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#ff9e64" }, + typeName: { color: "#0db9d7" }, + operator: { color: "#bb9af7" }, + "url,escape,regexp,link": { + color: "#b4f9f8", + }, + "meta,comment": { color: "#444b6a" }, + strong: { fontWeight: "bold" }, + emphasis: { fontStyle: "italic" }, + link: { textDecoration: "underline" }, + heading: { + fontWeight: "bold", + color: "#89ddff", + }, + "atom,bool,variableNameSpecial": { + color: "#c0caf5", + }, + invalid: { color: "#ff5370" }, + strikethrough: { + textDecoration: "line-through", + }, + } +); diff --git a/packages/code-motion/src/themes/tokyoNightDay.ts b/packages/code-motion/src/themes/tokyoNightDay.ts new file mode 100644 index 0000000..3565723 --- /dev/null +++ b/packages/code-motion/src/themes/tokyoNightDay.ts @@ -0,0 +1,60 @@ +import { makeTheme } from "@/theming"; + +export const tokyoNightDay = makeTheme( + "tokyoNightDay", + "dark", + { + background: { background: "#e1e2e7" }, + foreground: { color: "#3760bf" }, + caret: { color: "#3760bf" }, + selection: { color: "#99a7df" }, + selectionMatch: { color: "#99a7df" }, + gutter: { + background: "#e1e2e7", + color: "#3760bf", + }, + + lineHighlight: { color: "#5f5faf11" }, + keyword: { color: "#007197" }, + "name,deleted,character,macroName": { + color: "#3760bf", + }, + propertyName: { color: "#3760bf" }, + "processingInstruction,string,inserted,stringSpecial": + { color: "#587539" }, + variableNameFunction: { color: "#3760bf" }, + labelName: { color: "#3760bf" }, + "color,nameConstant,nameStandard": { + color: "#3760bf", + }, + "nameDefinition,separator": { + color: "#3760bf", + }, + className: { color: "#3760bf" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#b15c00" }, + typeName: { + color: "#007197", + fontStyle: "#007197", + }, + operator: { color: "#007197" }, + "url,escape,regexp,link": { + color: "#587539", + }, + "meta,comment": { color: "#848cb5" }, + strong: { fontWeight: "bold" }, + emphasis: { fontStyle: "italic" }, + link: { textDecoration: "underline" }, + heading: { + fontWeight: "bold", + color: "#b15c00", + }, + "atom,bool,variableNameSpecial": { + color: "#3760bf", + }, + invalid: { color: "#f52a65" }, + strikethrough: { + textDecoration: "line-through", + }, + } +); diff --git a/packages/code-motion/src/themes/tokyoNightStorm.ts b/packages/code-motion/src/themes/tokyoNightStorm.ts new file mode 100644 index 0000000..b1ba191 --- /dev/null +++ b/packages/code-motion/src/themes/tokyoNightStorm.ts @@ -0,0 +1,56 @@ +import { makeTheme } from "@/theming"; + +export const tokyoNightStorm = makeTheme( + "tokyoNightStorm", + "dark", + { + background: { background: "#1a1b26" }, + foreground: { color: "#787c99" }, + caret: { color: "#c0caf5" }, + selection: { color: "#515c7e40" }, + selectionMatch: { color: "#16161e" }, + gutter: { + background: "#1a1b26", + color: "#787c99", + }, + lineHighlight: { color: "#1e202e" }, + keyword: { color: "#bb9af7" }, + "name,deleted,character,macroName": { + color: "#c0caf5", + }, + propertyName: { color: "#7aa2f7" }, + "processingInstruction,string,stringSpecial,inserted": + { color: "#9ece6a" }, + variableNameFunction: { color: "#7aa2f7" }, + labelName: { color: "#7aa2f7" }, + "color,nameConstant,nameStandard": { + color: "#bb9af7", + }, + "nameDefinition,separator": { + color: "#c0caf5", + }, + className: { color: "#c0caf5" }, + "number,changed,annotation,modifier,self,namespace": + { color: "#ff9e64" }, + typeName: { color: "#0db9d7" }, + operator: { color: "#bb9af7" }, + "url,escape,regexp,link": { + color: "#b4f9f8", + }, + "meta,comment": { color: "#444b6a" }, + strong: { fontWeight: "bold" }, + emphasis: { fontStyle: "italic" }, + link: { textDecoration: "underline" }, + heading: { + fontWeight: "bold", + color: "#89ddff", + }, + "atom,bool,variableNameSpecial": { + color: "#c0caf5", + }, + invalid: { color: "#ff5370" }, + strikethrough: { + textDecoration: "line-through", + }, + } +); diff --git a/packages/code-motion/src/themes/vscode.ts b/packages/code-motion/src/themes/vscode.ts new file mode 100644 index 0000000..6e1301e --- /dev/null +++ b/packages/code-motion/src/themes/vscode.ts @@ -0,0 +1,58 @@ +/** + * https://github.com/uiwjs/react-codemirror/issues/409 + */ + +import { makeTheme } from "@/theming"; + +export const vscodeDark = makeTheme( + "vscode", + "dark", + { + background: { background: "#1e1e1e" }, + foreground: { color: "#9cdcfe" }, + caret: { color: "#c6c6c6" }, + selection: { color: "#6199ff2f" }, + selectionMatch: { color: "#72a1ff59" }, + lineHighlight: { color: "#ffffff0f" }, + gutter: { + background: "#1e1e1e", + color: "#838383", + }, + gutterActive: { color: "#fff" }, + + "keyword,operatorKeyword,modifier,color,nameConstant,nameStandard,nameStandardTag,braceSpecial,atom,bool,variableNameSpecial": + { color: "#569cd6" }, + "controlKeyword,moduleKeyword": { + color: "#c586c0", + }, + "name,deleted,character,macroName,propertyName,variableName,labelName,nameDefinition": + { color: "#9cdcfe" }, + heading: { + fontWeight: "bold", + color: "#9cdcfe", + }, + "typeName,className,tagName,number,changed,annotation,self,namespace": + { color: "#4ec9b0" }, + "variableNameFunction,propertyNameFunction": { + color: "#dcdcaa", + }, + number: { color: "#b5cea8" }, + "operator,punctuation,separator,url,escape,regexp": + { color: "#d4d4d4" }, + regexp: { color: "#d16969" }, + "stringSpecial,processingInstruction,string,inserted": + { color: "#ce9178" }, + angleBracket: { color: "#808080" }, + strong: { fontWeight: "bold" }, + emphasis: { fontStyle: "italic" }, + strikethrough: { + textDecoration: "line-through", + }, + "meta,comment": { color: "#6a9955" }, + link: { + color: "#6a9955", + textDecoration: "underline", + }, + invalid: { color: "#ff0000" }, + } +); diff --git a/packages/code-motion/src/themes/xcode.ts b/packages/code-motion/src/themes/xcode.ts new file mode 100644 index 0000000..8ef09aa --- /dev/null +++ b/packages/code-motion/src/themes/xcode.ts @@ -0,0 +1,56 @@ +/** + * @name Xcode + */ + +import { makeTheme } from "@/theming"; +export const xcodeLight = makeTheme( + "xcode", + "light", + { + background: { background: "#fff" }, + foreground: { color: "#3D3D3D" }, + selection: { color: "#BBDFFF" }, + selectionMatch: { color: "#BBDFFF" }, + gutter: { + background: "#fff", + color: "#AFAFAF", + }, + lineHighlight: { color: "#EDF4FF" }, + "comment,quote": { color: "#707F8D" }, + "typeName,typeOperator": { color: "#aa0d91" }, + keyword: { + color: "#aa0d91", + fontWeight: "bold", + }, + "string,meta": { color: "#D23423" }, + name: { color: "#032f62" }, + typeName: { color: "#522BB2" }, + variableName: { color: "#23575C" }, + variableNameDefinition: { color: "#327A9E" }, + "regexp,link": { color: "#0e0eff" }, + } +); + +export const xcodeDark = makeTheme( + "xcode", + "dark", + { + background: { background: "#292A30" }, + foreground: { color: "#CECFD0" }, + caret: { color: "#fff" }, + selection: { color: "#727377" }, + selectionMatch: { color: "#727377" }, + lineHighlight: { color: "#2F3239" }, + "comment,quote": { color: "#7F8C98" }, + keyword: { + color: "#FF7AB2", + fontWeight: "bold", + }, + "string,meta": { color: "#FF8170" }, + typeName: { color: "#DABAFF" }, + variableNameDefinition: { color: "#6BDFFF" }, + name: { color: "#6BAA9F" }, + variableName: { color: "#ACF2E4" }, + "regexp,link": { color: "#FF8170" }, + } +); diff --git a/packages/code-motion/src/theming/index.ts b/packages/code-motion/src/theming/index.ts new file mode 100644 index 0000000..178cd64 --- /dev/null +++ b/packages/code-motion/src/theming/index.ts @@ -0,0 +1 @@ +export * from "./utils"; diff --git a/packages/code-motion/src/theming/utils.ts b/packages/code-motion/src/theming/utils.ts new file mode 100644 index 0000000..e444981 --- /dev/null +++ b/packages/code-motion/src/theming/utils.ts @@ -0,0 +1,191 @@ +import { + Tag, + tagHighlighter, + tags, +} from "@lezer/highlight"; +import React from "react"; + +type ThemeObject = { + [key in + | ThemeKeys + | (string & {})]?: React.CSSProperties; +}; + +const DEFAULT_THEME_STYLES: ThemeObject = { + highlight: { + opacity: 1, + }, + unhighlight: { + opacity: 0.5, + }, + linter: { + textDecoration: "underline", + textDecorationStyle: "wavy", + textUnderlineOffset: "0.25em", + textDecorationThickness: "0.075em", + }, + + linterError: { + textDecorationColor: "#EF6262", + }, + linterWarning: { + textDecorationColor: "#F3AA60", + }, +} as const; + +const THEME_STYLE_KEYS = [ + "background", + "foreground", + "caret", + "selection", + "selectionMatch", + "gutter", + "gutterActive", + + "lineHighlight", + + "highlight", + "unhighlight", + "linter", + "linterError", + "linterWarning", +] as const; + +type Tags = typeof tags; + +type ModifierKeys = { + [key in keyof Tags]: Tags[key] extends ( + ...args: any[] + ) => unknown + ? key + : never; +}[keyof Tags]; + +type TagKeys = Exclude; + +type ThemeTagKeys = + | TagKeys + | `${TagKeys}${Capitalize}`; + +type ThemeKeys = + | (typeof THEME_STYLE_KEYS)[number] + | ThemeTagKeys; + +const InvalidKeys = new Set(); + +export const makeTheme = ( + name: string, + variant: string, + styles: ThemeObject +) => { + const highlighterRules: { + tag: Tag; + class: string; + }[] = []; + const styleMap = new Map< + string, + React.CSSProperties + >(); + for (const [key, value] of Object.entries({ + ...DEFAULT_THEME_STYLES, + ...styles, + })) { + if (!value) continue; + key.split(",").forEach((key) => { + const themeKey = key.trim(); + if ( + THEME_STYLE_KEYS.includes( + themeKey as (typeof THEME_STYLE_KEYS)[number] + ) + ) { + styleMap.set(themeKey, value); + return; + } + styleMap.set(themeKey, value); + let [rawKey, modifier] = themeKey.split( + /(function|definition|constant|function|standard|local|special)/i + ); + modifier = modifier?.toLowerCase(); + let tag = tags[rawKey as keyof typeof tags]; + if (!tag || typeof tag === "function") { + InvalidKeys.add(themeKey); + + console.warn( + `Invalid theme key "${themeKey}" on theme "${name}:${variant}"` + ); + return; + } + + if (modifier && modifier in tags) { + const modifierFn = + tags[modifier as keyof typeof tags]; + + if (typeof modifierFn === "function") { + tag = modifierFn(tag); + } + } + highlighterRules.push({ + tag, + class: themeKey, + }); + }); + } + + return { + name, + variant, + styles: styleMap, + highlighter: tagHighlighter(highlighterRules), + extend( + stylesExtension: ThemeObject, + name?: string, + variant?: string + ) { + return makeTheme( + name ?? this.name, + variant ?? this.variant, + { + ...styles, + ...stylesExtension, + } + ); + }, + }; +}; + +export type Theme = ReturnType; + +function toKebabCase(str: string) { + return str + .replace(/([a-z])([A-Z])/g, "$1-$2") + .toLowerCase(); +} + +export function computeStyles( + theme: Theme, + styles: string[] +) { + const out: React.CSSProperties = {}; + + for (const style of styles) { + const styleValue = theme.styles.get(style); + if (styleValue) { + Object.assign(out, styleValue); + } + } + + return out; +} +export const toCss = ( + styles: React.CSSProperties +) => { + return Object.entries(styles).reduce( + (acc, [key, value]) => { + if (typeof value !== "undefined") { + acc += `${toKebabCase(key)}:${value};`; + } + return acc; + }, + "" + ); +}; diff --git a/packages/code-motion/src/tokenizer/diffTokenizer.ts b/packages/code-motion/src/tokenizer/diffTokenizer.ts new file mode 100644 index 0000000..bcd2060 --- /dev/null +++ b/packages/code-motion/src/tokenizer/diffTokenizer.ts @@ -0,0 +1,32 @@ +import diff from "fast-diff"; + +import { Tokenizer } from "./tokenizer"; +import dedent from "ts-dedent"; + +export function diffTokenizer( + a: string, + b: string, + cleanup = true +) { + const tokenizer = new Tokenizer(); + diff(dedent(a), dedent(b), 0, cleanup).forEach( + ([op, code]) => { + switch (op) { + case diff.INSERT: + tokenizer.pushMorphing("", code); + break; + case diff.DELETE: + tokenizer.pushMorphing(code, ""); + break; + case diff.EQUAL: + tokenizer.pushStatic(code); + + break; + } + } + ); + + tokenizer.cleanEmpty(); + + return tokenizer.tokens; +} diff --git a/packages/code-motion/src/tokenizer/index.ts b/packages/code-motion/src/tokenizer/index.ts new file mode 100644 index 0000000..1fa05b3 --- /dev/null +++ b/packages/code-motion/src/tokenizer/index.ts @@ -0,0 +1,2 @@ +export * from "./tokenizer"; +export * from "./diffTokenizer"; diff --git a/packages/code-motion/src/tokenizer/ranges.ts b/packages/code-motion/src/tokenizer/ranges.ts new file mode 100644 index 0000000..ee18439 --- /dev/null +++ b/packages/code-motion/src/tokenizer/ranges.ts @@ -0,0 +1,292 @@ +import { LimitedApplyTo, Token } from "../types"; +import { Tokenizer } from "./tokenizer"; +import { isNumber } from "fp-ts/number"; +import { + Nullable, + isObject, + isString, + raise, +} from "@coord/core"; + +import { toEntries } from "fp-ts/Record"; +import { pipe } from "fp-ts/function"; + +export type Range = [number, number]; + +export const isRange = ( + range: unknown +): range is Range => + Array.isArray(range) && + isNumber(range[0]) && + isNumber(range[1]); + +export const normalizeRanges = ( + ranges: Range | Range[] +): Range[] => { + let rangesList: Range[]; + if (isRange(ranges)) { + rangesList = [ranges]; + } else { + rangesList = ranges; + } + rangesList.sort((a, b) => a[0] - b[0]); + + return rangesList.reduce( + (acc, range) => { + const last = acc.at(-1); + if (last && last[1] >= range[0]) { + last[1] = Math.max(last[1], range[1]); + return acc; + } + acc.push(range); + return acc; + }, + [] + ); +}; + +export const styleRange = + ( + ranges: Range | Range[], + styles: string[], + outOfRangeStyles: string[] = [], + applyTo: LimitedApplyTo = "future" + ) => + (tokens: Token[]) => { + if (ranges.length === 0) { + return tokens; + } + const rangesList = normalizeRanges(ranges); + const tokenizer = + Tokenizer.fromTokens(tokens); + + const mapUntil = + tokenizer.remapByPosition(applyTo); + const key = + applyTo === "future" + ? "styles" + : "pastStyles"; + const outOfRange = + outOfRangeStyles.length === 0 + ? (token: Token) => token + : (token: Token) => ({ + ...token, + [key]: [ + ...token[key], + ...outOfRangeStyles, + ], + }); + for (const [from, to] of rangesList) { + mapUntil(from, outOfRange); + mapUntil(to, (token) => { + return { + ...token, + [key]: [...token[key], ...styles], + }; + }); + } + mapUntil(Infinity, outOfRange); + + return tokenizer.tokens; + }; + +export type Rangeish = + | RangeQuery + | Range + | Range[]; + +export type RangeQuery = RegExp | string; + +const parseRangeString = (ocurrances: string) => { + const out = new Set(); + for (const ocurrance of ocurrances.split(",")) { + const [start, end] = ocurrance + .split("-") + .map(Number); + if (!Number.isFinite(start)) continue; + if (!Number.isFinite(end)) { + out.add(start); + continue; + } + for (let i = start; i <= end; i++) { + out.add(i); + } + } + if (out.size === 0) { + out.add(0); + } + return out; +}; + +const parseStringQuery = (query: string) => { + if ( + query.startsWith("{") && + query.endsWith("}") + ) { + const ocurrances = parseRangeString( + query.slice(1, -1) + ); + + if (ocurrances.size === 0) { + return () => []; + } + return (str: string) => { + const lines = str.split("\n"); + let pos = 0; + return lines.reduce( + (acc, line, i) => { + if (ocurrances.has(i)) { + acc.push([pos, pos + line.length]); + } + + pos += line.length + 1; + return acc; + }, + [] + ); + }; + } + + let queryString = query; + let ocurrances: Set | null = null; + const queryStringMatch = query.match( + /\/(.+)\/(.*)?/ + ); + if (queryStringMatch) { + const [ + , + queryStringFromMatch, + ocurrancesString, + ] = queryStringMatch; + if (queryStringFromMatch) + queryString = queryStringFromMatch; + if (ocurrancesString) + ocurrances = parseRangeString( + ocurrancesString + ); + } + + if (!queryString) return () => []; + + const shouldInclude = ocurrances + ? (i: number) => + (ocurrances ?? raise()).has(i) + : () => true; + return (str: string) => { + const regexp = new RegExp(queryString, "g"); + return [...str.matchAll(regexp)].reduce< + Range[] + >((acc, { index, 0: match }, i) => { + if ( + index === undefined || + !shouldInclude(i) + ) + return acc; + acc.push([index, index + match.length]); + return acc; + }, []); + }; +}; +export const queryRange = ( + str: string, + query: RegExp | string +) => { + const queryFn = isString(query) + ? parseStringQuery(query) + : (str: string) => + [...str.matchAll(query)].reduce( + (acc, { index, 0: match }) => { + if (index === undefined) return acc; + acc.push([ + index, + index + match.length, + ]); + return acc; + }, + [] + ); + + return queryFn(str); +}; + +export type RangeStyleMap = { + [key: string]: Nullable< + | Rangeish + | { + query: Rangeish; + outOfRangeStyles?: string; + outOfRangeOnly?: boolean; + } + >; +}; +export function queryRangeFromStyleMap( + str: string, + rangeMap: RangeStyleMap +) { + const ranges: { + styles: string[]; + outOfRangeStyles: string[]; + ranges: Range[]; + }[] = []; + for (let [styleId, query] of toEntries( + rangeMap + )) { + if (!query) continue; + let outOfRangeStyles: string[] = []; + let styles = styleId + .split(" ") + .filter(Boolean); + if (isObject(query)) { + outOfRangeStyles = + query.outOfRangeStyles + ?.split(" ") + .filter(Boolean) ?? outOfRangeStyles; + if (query.outOfRangeOnly) styles = []; + query = query.query; + } + if (Array.isArray(query)) { + ranges.push({ + styles, + outOfRangeStyles, + ranges: normalizeRanges(query), + }); + continue; + } + if ( + isString(query) || + query instanceof RegExp + ) { + ranges.push({ + styles, + outOfRangeStyles, + ranges: queryRange(str, query), + }); + continue; + } + } + return ranges; +} + +export const applyRanges = + ( + rangeStyles: { + styles: string[]; + outOfRangeStyles: string[]; + ranges: Range[]; + }[], + applyTo: LimitedApplyTo + ) => + (tokens: Token[]) => { + for (const range of rangeStyles) { + tokens = pipe( + tokens, + styleRange( + range.ranges, + range.styles, + range.outOfRangeStyles, + applyTo + ) + ); + } + return tokens; + }; diff --git a/packages/code-motion/src/tokenizer/tokenizer.ts b/packages/code-motion/src/tokenizer/tokenizer.ts new file mode 100644 index 0000000..88bf6d1 --- /dev/null +++ b/packages/code-motion/src/tokenizer/tokenizer.ts @@ -0,0 +1,404 @@ +import { Nullable } from "vitest"; +import { LimitedApplyTo, Token } from "../types"; +import { + isNumber, + isString, + isUndefined, + raise, +} from "@coord/core"; +import { + existsInTheFuture, + existsInThePast, +} from "./utils"; + +type TokenInput = + | [Nullable, Nullable] + | string + | number; + +export const tokenize = ( + src: TemplateStringsArray | string, + ...inputs: (TokenInput | string | number)[] +) => { + const tokenizer = new Tokenizer(); + const strings = isString(src) + ? [src] + : [...src]; + + for (let i = 0; i < strings.length; i++) { + const region = strings[i] ?? raise(); + tokenizer.pushStatic(region); + const input = inputs[i]; + if (isUndefined(input)) continue; + + if (isString(input) || isNumber(input)) { + tokenizer.pushStatic(String(input)); + continue; + } + const [past, future] = input; + + tokenizer.pushMorphing( + past ?? "", + future ?? "" + ); + } + tokenizer.trim(); + + return tokenizer.tokens; +}; + +export class Tokenizer { + tokens: Token[] = []; + indent = Infinity; + get last() { + return this.tokens.at(-1); + } + + static fromTokens(tokens: Token[]) { + const tokenizer = new Tokenizer(); + tokenizer.tokens = tokens; + return tokenizer; + } + + pushStatic(str: string) { + this.tokens.push( + ...this.processString(str, "static") + ); + } + + processString( + str: string, + type: Token["type"] + ): Token[] { + return str + .split("\n") + .reduce((acc, line, i, list) => { + const considerThisLineIndent = + i > 0 || !this.last; + const content = considerThisLineIndent + ? line.trimStart() + : line; + + const indent = + line.length - content.length; + + if ( + considerThisLineIndent && + content.length + ) { + this.indent = Math.min( + this.indent, + indent + ); + } + const isLastLine = i === list.length - 1; + const skipLine = + list.length > 1 && !isLastLine; + + acc.push({ + type, + content, + indent: indent, + skipLines: skipLine ? 1 : 0, + pastStyles: [], + styles: [], + }); + return acc; + }, []); + } + + dedent() { + for (const token of this.tokens) { + token.indent = Math.max( + token.indent - this.indent, + 0 + ); + } + } + trimStart() { + let pastIsTrimmed = false; + let indent = 0; + this.flatRemapPast((token) => { + if (pastIsTrimmed) return [token]; + const isEmpty = !/\S/.test(token.content); + + if (isEmpty) { + if (token.skipLines) { + indent = 0; + } + indent = Math.max(token.indent, indent); + if (token.type === "static") { + token.type = "insertion"; + return [token]; + } + return []; + } + pastIsTrimmed = true; + token.indent = Math.max( + indent, + token.indent + ); + return [token]; + }); + let futureIsTrimmed = false; + indent = 0; + this.flatRemapFuture((token) => { + if (futureIsTrimmed) return [token]; + const isEmpty = !/\S/.test(token.content); + + if (isEmpty) { + if (token.skipLines) { + indent = 0; + } + indent = Math.max(token.indent, indent); + if (token.type === "static") { + token.type = "deletion"; + return [token]; + } + return []; + } + futureIsTrimmed = true; + token.indent = Math.max( + indent, + token.indent + ); + return [token]; + }); + } + + trimEnd() { + let pastIsTrimmed = false; + + this.flatRemapPast((token) => { + if (pastIsTrimmed) return [token]; + + const isEmpty = !/\S/.test(token.content); + if (isEmpty) { + if (token.type === "static") { + token.type = "insertion"; + return [token]; + } + return []; + } + pastIsTrimmed = true; + token.skipLines = 0; + return [token]; + }, true); + let futureIsTrimmed = false; + + this.flatRemapFuture((token) => { + if (futureIsTrimmed) return [token]; + + const isEmpty = !/\S/.test(token.content); + if (isEmpty) { + if (token.type === "static") { + token.type = "deletion"; + return [token]; + } + return []; + } + futureIsTrimmed = true; + token.skipLines = 0; + return [token]; + }, true); + } + + cleanEmpty() { + this.tokens = this.tokens.reduce( + (acc, token, i) => { + if (token.content !== "") { + acc.push(token); + return acc; + } + if ( + token.indent && + token.skipLines === 0 + ) { + const next = this.tokens[i + 1]; + if (next && next.type === token.type) { + next.indent += token.indent; + } else { + acc.push(token); + } + } + + if (token.skipLines) { + const prev = acc[acc.length - 1]; + if (prev && prev.type === token.type) { + prev.skipLines += token.skipLines; + } else { + acc.push(token); + } + } + return acc; + }, + [] + ); + } + trim() { + this.dedent(); + this.trimStart(); + this.trimEnd(); + this.cleanEmpty(); + } + + pushMorphing( + deletion: string, + insetion: string + ) { + this.tokens.push( + ...this.processString(deletion, "deletion") + ); + + this.tokens.push( + ...this.processString(insetion, "insertion") + ); + } + + flatRemapPast( + fn: (token: Token) => Token[], + reverse = false + ) { + this.tokens = this.tokens[ + reverse ? "reduceRight" : "reduce" + ]((acc, token) => { + if (!existsInThePast(token)) { + acc.push(token); + return acc; + } + acc.push(...fn(token)); + return acc; + }, []); + if (reverse) { + this.tokens.reverse(); + } + } + + flatRemapFuture( + fn: (token: Token) => Token[], + reverse = false + ) { + this.tokens = this.tokens[ + reverse ? "reduceRight" : "reduce" + ]((acc, token) => { + if (!existsInTheFuture(token)) { + acc.push(token); + return acc; + } + acc.push(...fn(token)); + return acc; + }, []); + if (reverse) { + this.tokens.reverse(); + } + } + + stringifyPast() { + return this.tokens.reduce((acc, token) => { + if (existsInThePast(token)) { + if (token.indent) { + acc += " ".repeat(token.indent); + } + acc += token.content; + + if (token.skipLines) { + acc += "\n".repeat(token.skipLines); + } + } + return acc; + }, ""); + } + remapByPosition = (when: "past" | "future") => { + let position = 0; + const stack = [...this.tokens].reverse(); + this.tokens = []; + + return ( + to: number, + fn: (token: Token) => Token + ) => { + while (position < to) { + const token = stack[stack.length - 1]; + + if (!token) { + break; + } + if ( + (when === "past" && + !existsInThePast(token)) || + (when === "future" && + !existsInTheFuture(token)) + ) { + stack.pop(); + this.tokens.push(token); + continue; + } + + const endPosition = + position + + token.content.length + + token.skipLines + + token.indent; + + if (endPosition > to) { + break; + } + this.tokens.push(fn(token)); + position = endPosition; + stack.pop(); + } + + if (position === to) return; + + let token = stack.pop(); + if (!token) return; + token = { ...token }; + + const split = to - position - token.indent; + if (split < 0) { + token.indent += split; + + token.content = + " ".repeat(-split) + token.content; + } + position = to; + const newToken: Token = { + ...token, + content: token.content.slice(0, split), + skipLines: 0, + }; + + token.content = token.content.slice(split); + token.indent = 0; + stack.push(token); + this.tokens.push(fn(newToken)); + }; + }; + stringifyFuture() { + return this.tokens.reduce((acc, token) => { + if (existsInTheFuture(token)) { + if (token.indent) { + acc += " ".repeat(token.indent); + } + acc += token.content; + + if (token.skipLines) { + acc += "\n".repeat(token.skipLines); + } + } + return acc; + }, ""); + } +} + +export const stringifyTokens = + (applyTo: LimitedApplyTo = "future") => + (tokens: Token[]) => + Tokenizer.fromTokens(tokens)[ + applyTo === "future" + ? "stringifyFuture" + : "stringifyPast" + ](); + +// Alias +export const code = tokenize; diff --git a/packages/code-motion/src/tokenizer/utils.ts b/packages/code-motion/src/tokenizer/utils.ts new file mode 100644 index 0000000..12039ef --- /dev/null +++ b/packages/code-motion/src/tokenizer/utils.ts @@ -0,0 +1,11 @@ +import { Token } from "../types"; + +export const existsInThePast = (token: Token) => { + return token.type !== "insertion"; +}; + +export const existsInTheFuture = ( + token: Token +) => { + return token.type !== "deletion"; +}; diff --git a/packages/code-motion/src/types.ts b/packages/code-motion/src/types.ts new file mode 100644 index 0000000..937f203 --- /dev/null +++ b/packages/code-motion/src/types.ts @@ -0,0 +1,11 @@ +export type Token = { + content: string; + skipLines: number; + indent: number; + styles: string[]; + type: "static" | "insertion" | "deletion"; + pastStyles: string[]; +}; + +export type ApplyTo = "past" | "future" | "both"; +export type LimitedApplyTo = "past" | "future"; diff --git a/packages/code-motion/test.txt b/packages/code-motion/test.txt new file mode 100644 index 0000000..0a3d86e --- /dev/null +++ b/packages/code-motion/test.txt @@ -0,0 +1,798 @@ + +> @coord/code-motion@0.3.0 lint /Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion +> eslint . + + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/index.cjs + 3:13 error Require statement not part of import statement @typescript-eslint/no-var-requires + 41:17 error 'result' is never reassigned. Use 'const' instead prefer-const + 96:13 error 'props' is never reassigned. Use 'const' instead prefer-const + 97:13 error 'flags' is never reassigned. Use 'const' instead prefer-const + 99:13 error 'type' is never reassigned. Use 'const' instead prefer-const + 130:17 error 'group' is never reassigned. Use 'const' instead prefer-const + 142:13 error 'direct' is never reassigned. Use 'const' instead prefer-const + 143:18 error 'prop' is never reassigned. Use 'const' instead prefer-const + 144:22 error 'name' is never reassigned. Use 'const' instead prefer-const + 148:21 error 'found' is never reassigned. Use 'const' instead prefer-const + 179:13 error 'newTypes' is never reassigned. Use 'const' instead prefer-const + 180:18 error 'type' is never reassigned. Use 'const' instead prefer-const + 182:22 error 'source' is never reassigned. Use 'const' instead prefer-const + 183:21 error 'add' is never reassigned. Use 'const' instead prefer-const + 254:23 error 'prop' is never reassigned. Use 'const' instead prefer-const + 254:29 error 'value' is never reassigned. Use 'const' instead prefer-const + 260:13 error 'mounted' is never reassigned. Use 'const' instead prefer-const + 264:18 error 'ch' is never reassigned. Use 'const' instead prefer-const + 265:17 error 'str' is never reassigned. Use 'const' instead prefer-const + 285:29 warning 'mode' is assigned a value but never used @typescript-eslint/no-unused-vars + 286:13 error 'scope' is never reassigned. Use 'const' instead prefer-const + 287:13 error 'cursor' is never reassigned. Use 'const' instead prefer-const + 307:13 error 'node' is never reassigned. Use 'const' instead prefer-const + 317:13 error 'node' is never reassigned. Use 'const' instead prefer-const + 327:15 error 'enter' is never reassigned. Use 'const' instead prefer-const + 327:22 error 'leave' is never reassigned. Use 'const' instead prefer-const + 327:29 error 'from' is never reassigned. Use 'const' instead prefer-const + 327:39 error 'to' is never reassigned. Use 'const' instead prefer-const + 328:13 error 'mode' is never reassigned. Use 'const' instead prefer-const + 328:36 error 'anon' is never reassigned. Use 'const' instead prefer-const + 356:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 358:22 error 'id' is never reassigned. Use 'const' instead prefer-const + 409:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 418:13 error 'id' is never reassigned. Use 'const' instead prefer-const + 418:38 error 'endIndex' is never reassigned. Use 'const' instead prefer-const + 419:13 error 'type' is never reassigned. Use 'const' instead prefer-const + 425:13 error 'children' is never reassigned. Use 'const' instead prefer-const + 434:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 446:13 error 'b' is never reassigned. Use 'const' instead prefer-const + 447:13 error 'copy' is never reassigned. Use 'const' instead prefer-const + 451:17 error 'to' is never reassigned. Use 'const' instead prefer-const + 471:13 error 'last' is never reassigned. Use 'const' instead prefer-const + 490:13 error 'parent' is never reassigned. Use 'const' instead prefer-const + 495:9 error 'mode' is never reassigned. Use 'const' instead prefer-const + 503:13 error 'inner' is never reassigned. Use 'const' instead prefer-const + 524:21 error 'next' is never reassigned. Use 'const' instead prefer-const + 524:41 error 'start' is never reassigned. Use 'const' instead prefer-const + 530:25 error 'index' is never reassigned. Use 'const' instead prefer-const + 539:25 error 'inner' is never reassigned. Use 'const' instead prefer-const + 562:17 error 'rPos' is never reassigned. Use 'const' instead prefer-const + 563:24 error 'from' is never reassigned. Use 'const' instead prefer-const + 563:30 error 'to' is never reassigned. Use 'const' instead prefer-const + 597:13 error 'r' is never reassigned. Use 'const' instead prefer-const + 609:9 error 'cur' is never reassigned. Use 'const' instead prefer-const + 609:30 error 'result' is never reassigned. Use 'const' instead prefer-const + 656:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 657:13 error 'index' is never reassigned. Use 'const' instead prefer-const + 667:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 668:13 error 'index' is never reassigned. Use 'const' instead prefer-const + 678:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 679:13 error 'after' is never reassigned. Use 'const' instead prefer-const + 685:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 686:13 error 'parentStart' is never reassigned. Use 'const' instead prefer-const + 694:13 error 'children' is never reassigned. Use 'const' instead prefer-const + 694:28 error 'positions' is never reassigned. Use 'const' instead prefer-const + 695:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 696:13 error 'startI' is never reassigned. Use 'const' instead prefer-const + 696:38 error 'endI' is never reassigned. Use 'const' instead prefer-const + 698:17 error 'from' is never reassigned. Use 'const' instead prefer-const + 714:13 error 'r' is never reassigned. Use 'const' instead prefer-const + 762:15 error 'start' is never reassigned. Use 'const' instead prefer-const + 762:22 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 786:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 787:13 error 'index' is never reassigned. Use 'const' instead prefer-const + 818:13 error 'parent' is never reassigned. Use 'const' instead prefer-const + 828:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 828:39 error 'd' is never reassigned. Use 'const' instead prefer-const + 830:17 error 'parentStart' is never reassigned. Use 'const' instead prefer-const + 835:17 error 'after' is never reassigned. Use 'const' instead prefer-const + 846:30 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 865:25 error 'child' is never reassigned. Use 'const' instead prefer-const + 915:13 error 'cache' is never reassigned. Use 'const' instead prefer-const + 974:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 974:41 error 'types' is never reassigned. Use 'const' instead prefer-const + 978:17 error 'type' is never reassigned. Use 'const' instead prefer-const + 993:11 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 993:19 error 'nodeSet' is never reassigned. Use 'const' instead prefer-const + 993:28 error 'maxBufferLength' is never reassigned. Use 'const' instead prefer-const + 993:67 error 'reused' is never reassigned. Use 'const' instead prefer-const + 993:80 error 'minRepeatType' is never reassigned. Use 'const' instead prefer-const + 994:9 error 'cursor' is never reassigned. Use 'const' instead prefer-const + 995:9 error 'types' is never reassigned. Use 'const' instead prefer-const + 998:15 error 'id' is never reassigned. Use 'const' instead prefer-const + 998:19 error 'start' is never reassigned. Use 'const' instead prefer-const + 998:26 error 'end' is never reassigned. Use 'const' instead prefer-const + 998:31 error 'size' is never reassigned. Use 'const' instead prefer-const + 999:13 error 'lookAheadAtStart' is never reassigned. Use 'const' instead prefer-const + 1003:21 error 'node' is never reassigned. Use 'const' instead prefer-const + 1020:13 error 'type' is never reassigned. Use 'const' instead prefer-const + 1024:17 error 'data' is never reassigned. Use 'const' instead prefer-const + 1025:17 error 'endPos' is never reassigned. Use 'const' instead prefer-const + 1032:17 error 'endPos' is never reassigned. Use 'const' instead prefer-const + 1034:17 error 'localChildren' is never reassigned. Use 'const' instead prefer-const + 1034:37 error 'localPositions' is never reassigned. Use 'const' instead prefer-const + 1035:17 error 'localInRepeat' is never reassigned. Use 'const' instead prefer-const + 1055:21 error 'make' is never reassigned. Use 'const' instead prefer-const + 1067:32 error 'lastI' is never reassigned. Use 'const' instead prefer-const + 1078:13 error 'localChildren' is never reassigned. Use 'const' instead prefer-const + 1078:33 error 'localPositions' is never reassigned. Use 'const' instead prefer-const + 1088:17 error 'pair' is never reassigned. Use 'const' instead prefer-const + 1092:17 error 'pair' is never reassigned. Use 'const' instead prefer-const + 1104:13 error 'fork' is never reassigned. Use 'const' instead prefer-const + 1105:44 error 'minStart' is never reassigned. Use 'const' instead prefer-const + 1106:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 1108:17 error 'nodeSize' is never reassigned. Use 'const' instead prefer-const + 1121:17 error 'startPos' is never reassigned. Use 'const' instead prefer-const + 1125:17 error 'nodeStart' is never reassigned. Use 'const' instead prefer-const + 1151:15 error 'id' is never reassigned. Use 'const' instead prefer-const + 1151:19 error 'start' is never reassigned. Use 'const' instead prefer-const + 1151:26 error 'end' is never reassigned. Use 'const' instead prefer-const + 1151:31 error 'size' is never reassigned. Use 'const' instead prefer-const + 1154:17 error 'startIndex' is never reassigned. Use 'const' instead prefer-const + 1156:21 error 'endPos' is never reassigned. Use 'const' instead prefer-const + 1173:9 error 'children' is never reassigned. Use 'const' instead prefer-const + 1173:24 error 'positions' is never reassigned. Use 'const' instead prefer-const + 1176:9 error 'length' is never reassigned. Use 'const' instead prefer-const + 1186:18 error 'child' is never reassigned. Use 'const' instead prefer-const + 1215:9 error 'maxChild' is never reassigned. Use 'const' instead prefer-const + 1216:9 error 'localChildren' is never reassigned. Use 'const' instead prefer-const + 1216:29 error 'localPositions' is never reassigned. Use 'const' instead prefer-const + 1219:17 error 'groupFrom' is never reassigned. Use 'const' instead prefer-const + 1219:32 error 'groupStart' is never reassigned. Use 'const' instead prefer-const + 1222:21 error 'nextSize' is never reassigned. Use 'const' instead prefer-const + 1229:25 error 'only' is never reassigned. Use 'const' instead prefer-const + 1236:21 error 'length' is never reassigned. Use 'const' instead prefer-const + 1263:13 error 'parse' is never reassigned. Use 'const' instead prefer-const + 1265:17 error 'done' is never reassigned. Use 'const' instead prefer-const + 1339:13 error 'tag' is never reassigned. Use 'const' instead prefer-const + 1342:22 error 't' is never reassigned. Use 'const' instead prefer-const + 1359:13 error 'mod' is never reassigned. Use 'const' instead prefer-const + 1376:13 error 'exists' is never reassigned. Use 'const' instead prefer-const + 1379:13 error 'set' is never reassigned. Use 'const' instead prefer-const + 1379:23 error 'tag' is never reassigned. Use 'const' instead prefer-const + 1380:18 error 'm' is never reassigned. Use 'const' instead prefer-const + 1382:13 error 'configs' is never reassigned. Use 'const' instead prefer-const + 1383:18 error 'parent' is never reassigned. Use 'const' instead prefer-const + 1385:26 error 'config' is never reassigned. Use 'const' instead prefer-const + 1394:9 error 'sets' is never reassigned. Use 'const' instead prefer-const + 1454:9 error 'byName' is never reassigned. Use 'const' instead prefer-const + 1455:14 error 'prop' is never reassigned. Use 'const' instead prefer-const + 1459:18 error 'part' is never reassigned. Use 'const' instead prefer-const + 1461:21 error 'pieces' is never reassigned. Use 'const' instead prefer-const + 1467:25 error 'm' is never reassigned. Use 'const' instead prefer-const + 1474:25 error 'next' is never reassigned. Use 'const' instead prefer-const + 1483:21 error 'last' is never reassigned. Use 'const' instead prefer-const + 1483:47 error 'inner' is never reassigned. Use 'const' instead prefer-const + 1486:21 error 'rule' is never reassigned. Use 'const' instead prefer-const + 1519:9 error 'map' is never reassigned. Use 'const' instead prefer-const + 1520:14 error 'style' is never reassigned. Use 'const' instead prefer-const + 1524:22 error 'tag' is never reassigned. Use 'const' instead prefer-const + 1527:11 error 'scope' is never reassigned. Use 'const' instead prefer-const + 1527:18 error 'all' is never reassigned. Use 'const' instead prefer-const + 1531:22 error 'tag' is never reassigned. Use 'const' instead prefer-const + 1532:26 error 'sub' is never reassigned. Use 'const' instead prefer-const + 1533:25 error 'tagClass' is never reassigned. Use 'const' instead prefer-const + 1547:14 error 'highlighter' is never reassigned. Use 'const' instead prefer-const + 1548:13 error 'value' is never reassigned. Use 'const' instead prefer-const + 1573:9 error 'builder' is never reassigned. Use 'const' instead prefer-const + 1597:15 error 'type' is never reassigned. Use 'const' instead prefer-const + 1597:27 error 'start' is never reassigned. Use 'const' instead prefer-const + 1597:38 error 'end' is never reassigned. Use 'const' instead prefer-const + 1603:13 error 'rule' is never reassigned. Use 'const' instead prefer-const + 1604:13 error 'tagCls' is never reassigned. Use 'const' instead prefer-const + 1615:13 error 'mounted' is never reassigned. Use 'const' instead prefer-const + 1617:17 error 'inner' is never reassigned. Use 'const' instead prefer-const + 1618:17 error 'innerHighlighters' is never reassigned. Use 'const' instead prefer-const + 1619:17 error 'hasChild' is never reassigned. Use 'const' instead prefer-const + 1621:21 error 'next' is never reassigned. Use 'const' instead prefer-const + 1622:21 error 'nextPos' is never reassigned. Use 'const' instead prefer-const + 1623:21 error 'rangeFrom' is never reassigned. Use 'const' instead prefer-const + 1623:54 error 'rangeTo' is never reassigned. Use 'const' instead prefer-const + 2184:17 error 'themeKey' is never reassigned. Use 'const' instead prefer-const + 3846:21 error 'split' is never reassigned. Use 'const' instead prefer-const + 5352:13 error 'cx' is never reassigned. Use 'const' instead prefer-const + 5371:13 error 'depth' is never reassigned. Use 'const' instead prefer-const + 5371:65 error 'type' is never reassigned. Use 'const' instead prefer-const + 5372:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5373:13 error 'dPrec' is never reassigned. Use 'const' instead prefer-const + 5390:13 error 'base' is never reassigned. Use 'const' instead prefer-const + 5391:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 5391:74 error 'size' is never reassigned. Use 'const' instead prefer-const + 5406:13 error 'bufferBase' is never reassigned. Use 'const' instead prefer-const + 5406:59 error 'count' is never reassigned. Use 'const' instead prefer-const + 5409:17 error 'pos' is never reassigned. Use 'const' instead prefer-const + 5416:17 error 'baseStateID' is never reassigned. Use 'const' instead prefer-const + 5468:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 5473:17 error 'nextState' is never reassigned. Use 'const' instead prefer-const + 5473:39 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5507:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 5527:13 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 5527:48 error 'base' is never reassigned. Use 'const' instead prefer-const + 5536:13 error 'isNode' is never reassigned. Use 'const' instead prefer-const + 5549:17 error 'action' is never reassigned. Use 'const' instead prefer-const + 5565:17 error 'best' is never reassigned. Use 'const' instead prefer-const + 5572:25 error 's' is never reassigned. Use 'const' instead prefer-const + 5578:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 5580:17 error 's' is never reassigned. Use 'const' instead prefer-const + 5583:17 error 'stack' is never reassigned. Use 'const' instead prefer-const + 5596:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5601:17 error 'depth' is never reassigned. Use 'const' instead prefer-const + 5601:69 error 'term' is never reassigned. Use 'const' instead prefer-const + 5602:17 error 'target' is never reassigned. Use 'const' instead prefer-const + 5604:21 error 'backup' is never reassigned. Use 'const' instead prefer-const + 5620:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5620:34 error 'seen' is never reassigned. Use 'const' instead prefer-const + 5621:13 error 'explore' is never reassigned. Use 'const' instead prefer-const + 5628:25 error 'rDepth' is never reassigned. Use 'const' instead prefer-const + 5630:29 error 'term' is never reassigned. Use 'const' instead prefer-const + 5630:75 error 'target' is never reassigned. Use 'const' instead prefer-const + 5636:25 error 'found' is never reassigned. Use 'const' instead prefer-const + 5660:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5695:13 error 'last' is never reassigned. Use 'const' instead prefer-const + 5701:13 error 'last' is never reassigned. Use 'const' instead prefer-const + 5707:17 error 'newCx' is never reassigned. Use 'const' instead prefer-const + 5755:13 error 'term' is never reassigned. Use 'const' instead prefer-const + 5755:59 error 'depth' is never reassigned. Use 'const' instead prefer-const + 5765:13 error 'goto' is never reassigned. Use 'const' instead prefer-const + 5784:13 error 'next' is never reassigned. Use 'const' instead prefer-const + 5892:17 error 'next' is never reassigned. Use 'const' instead prefer-const + 5899:17 error 'next' is never reassigned. Use 'const' instead prefer-const + 5909:18 error 'range' is never reassigned. Use 'const' instead prefer-const + 5924:13 error 'idx' is never reassigned. Use 'const' instead prefer-const + 5930:17 error 'resolved' is never reassigned. Use 'const' instead prefer-const + 5955:13 error 'end' is never reassigned. Use 'const' instead prefer-const + 5963:19 error 'chunk' is never reassigned. Use 'const' instead prefer-const + 5963:26 error 'chunkPos' is never reassigned. Use 'const' instead prefer-const + 5973:17 error 'nextChunk' is never reassigned. Use 'const' instead prefer-const + 5974:17 error 'end' is never reassigned. Use 'const' instead prefer-const + 6051:18 error 'r' is never reassigned. Use 'const' instead prefer-const + 6067:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6080:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 6082:17 error 'atEof' is never reassigned. Use 'const' instead prefer-const + 6082:41 error 'nextPos' is never reassigned. Use 'const' instead prefer-const + 6139:20 error 'groupMask' is never reassigned. Use 'const' instead prefer-const + 6139:46 error 'dialect' is never reassigned. Use 'const' instead prefer-const + 6143:13 error 'accEnd' is never reassigned. Use 'const' instead prefer-const + 6149:21 error 'term' is never reassigned. Use 'const' instead prefer-const + 6157:13 error 'next' is never reassigned. Use 'const' instead prefer-const + 6165:17 error 'mid' is never reassigned. Use 'const' instead prefer-const + 6166:17 error 'index' is never reassigned. Use 'const' instead prefer-const + 6167:17 error 'from' is never reassigned. Use 'const' instead prefer-const + 6167:37 error 'to' is never reassigned. Use 'const' instead prefer-const + 6188:9 error 'iPrev' is never reassigned. Use 'const' instead prefer-const + 6193:82 error LOG is not listed as a dependency in turbo.json turbo/no-undeclared-env-vars + 6200:9 error 'cursor' is never reassigned. Use 'const' instead prefer-const + 6229:13 error 'fr' is never reassigned. Use 'const' instead prefer-const + 6256:17 error 'last' is never reassigned. Use 'const' instead prefer-const + 6261:17 error 'top' is never reassigned. Use 'const' instead prefer-const + 6261:41 error 'index' is never reassigned. Use 'const' instead prefer-const + 6268:17 error 'next' is never reassigned. Use 'const' instead prefer-const + 6269:17 error 'start' is never reassigned. Use 'const' instead prefer-const + 6278:25 error 'end' is never reassigned. Use 'const' instead prefer-const + 6280:29 error 'lookAhead' is never reassigned. Use 'const' instead prefer-const + 6305:45 warning '_' is defined but never used @typescript-eslint/no-unused-vars + 6310:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6310:37 error 'tokenizers' is never reassigned. Use 'const' instead prefer-const + 6311:13 error 'mask' is never reassigned. Use 'const' instead prefer-const + 6312:13 error 'context' is never reassigned. Use 'const' instead prefer-const + 6317:17 error 'tokenizer' is never reassigned. Use 'const' instead prefer-const + 6317:44 error 'token' is never reassigned. Use 'const' instead prefer-const + 6328:21 error 'startIndex' is never reassigned. Use 'const' instead prefer-const + 6355:13 error 'main' is never reassigned. Use 'const' instead prefer-const + 6355:39 error 'pos' is never reassigned. Use 'const' instead prefer-const + 6355:44 error 'p' is never reassigned. Use 'const' instead prefer-const + 6362:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 6365:19 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6368:25 error 'result' is never reassigned. Use 'const' instead prefer-const + 6394:15 error 'state' is never reassigned. Use 'const' instead prefer-const + 6394:34 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6394:56 error 'data' is never reassigned. Use 'const' instead prefer-const + 6449:15 error 'from' is never reassigned. Use 'const' instead prefer-const + 6464:13 error 'stacks' is never reassigned. Use 'const' instead prefer-const + 6464:35 error 'pos' is never reassigned. Use 'const' instead prefer-const + 6466:13 error 'newStacks' is never reassigned. Use 'const' instead prefer-const + 6476:18 error 's' is never reassigned. Use 'const' instead prefer-const + 6484:17 error 'stack' is never reassigned. Use 'const' instead prefer-const + 6499:25 error 'tok' is never reassigned. Use 'const' instead prefer-const + 6506:17 error 'finished' is never reassigned. Use 'const' instead prefer-const + 6518:17 error 'finished' is never reassigned. Use 'const' instead prefer-const + 6524:17 error 'maxRemaining' is never reassigned. Use 'const' instead prefer-const + 6538:21 error 'stack' is never reassigned. Use 'const' instead prefer-const + 6540:25 error 'other' is never reassigned. Use 'const' instead prefer-const + 6572:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 6572:34 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6573:13 error 'base' is never reassigned. Use 'const' instead prefer-const + 6577:17 error 'strictCx' is never reassigned. Use 'const' instead prefer-const + 6577:81 error 'cxHash' is never reassigned. Use 'const' instead prefer-const + 6579:21 error 'match' is never reassigned. Use 'const' instead prefer-const + 6588:21 error 'inner' is never reassigned. Use 'const' instead prefer-const + 6595:13 error 'defaultReduce' is never reassigned. Use 'const' instead prefer-const + 6605:13 error 'actions' is never reassigned. Use 'const' instead prefer-const + 6607:17 error 'action' is never reassigned. Use 'const' instead prefer-const + 6607:40 error 'term' is never reassigned. Use 'const' instead prefer-const + 6607:61 error 'end' is never reassigned. Use 'const' instead prefer-const + 6608:17 error 'last' is never reassigned. Use 'const' instead prefer-const + 6609:17 error 'localStack' is never reassigned. Use 'const' instead prefer-const + 6627:13 error 'pos' is never reassigned. Use 'const' instead prefer-const + 6640:17 error 'stack' is never reassigned. Use 'const' instead prefer-const + 6641:17 error 'base' is never reassigned. Use 'const' instead prefer-const + 6649:21 error 'done' is never reassigned. Use 'const' instead prefer-const + 6653:17 error 'force' is never reassigned. Use 'const' instead prefer-const + 6657:21 error 'done' is never reassigned. Use 'const' instead prefer-const + 6663:22 error 'insert' is never reassigned. Use 'const' instead prefer-const + 6705:13 error 'other' is never reassigned. Use 'const' instead prefer-const + 6755:13 error 'nodeNames' is never reassigned. Use 'const' instead prefer-const + 6759:13 error 'topTerms' is never reassigned. Use 'const' instead prefer-const + 6760:13 error 'nodeProps' is never reassigned. Use 'const' instead prefer-const + 6767:22 error 'propSpec' is never reassigned. Use 'const' instead prefer-const + 6772:25 error 'next' is never reassigned. Use 'const' instead prefer-const + 6777:29 error 'value' is never reassigned. Use 'const' instead prefer-const + 6796:13 error 'tokenArray' is never reassigned. Use 'const' instead prefer-const + 6819:18 error 'w' is never reassigned. Use 'const' instead prefer-const + 6825:13 error 'table' is never reassigned. Use 'const' instead prefer-const + 6829:17 error 'groupTag' is never reassigned. Use 'const' instead prefer-const + 6829:42 error 'last' is never reassigned. Use 'const' instead prefer-const + 6830:17 error 'target' is never reassigned. Use 'const' instead prefer-const + 6842:13 error 'data' is never reassigned. Use 'const' instead prefer-const + 6873:13 error 'deflt' is never reassigned. Use 'const' instead prefer-const + 6889:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 6898:21 error 'value' is never reassigned. Use 'const' instead prefer-const + 6911:13 error 'copy' is never reassigned. Use 'const' instead prefer-const + 6915:17 error 'info' is never reassigned. Use 'const' instead prefer-const + 6922:21 error 'found' is never reassigned. Use 'const' instead prefer-const + 6928:21 error 'found' is never reassigned. Use 'const' instead prefer-const + 6931:21 error 'spec' is never reassigned. Use 'const' instead prefer-const + 6967:13 error 'prec' is never reassigned. Use 'const' instead prefer-const + 6972:13 error 'values' is never reassigned. Use 'const' instead prefer-const + 6972:50 error 'flags' is never reassigned. Use 'const' instead prefer-const + 6974:22 error 'part' is never reassigned. Use 'const' instead prefer-const + 6975:21 error 'id' is never reassigned. Use 'const' instead prefer-const + 6996:14 error 'stack' is never reassigned. Use 'const' instead prefer-const + 6997:13 error 'stopped' is never reassigned. Use 'const' instead prefer-const + 7007:13 error 'mask' is never reassigned. Use 'const' instead prefer-const + 7040:8 error 'next' is never reassigned. Use 'const' instead prefer-const + 7046:8 error 'next' is never reassigned. Use 'const' instead prefer-const + 7054:8 error 'next' is never reassigned. Use 'const' instead prefer-const + 7059:11 error 'mayPostfix' is never reassigned. Use 'const' instead prefer-const + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/index.js + 39:17 error 'result' is never reassigned. Use 'const' instead prefer-const + 94:13 error 'props' is never reassigned. Use 'const' instead prefer-const + 95:13 error 'flags' is never reassigned. Use 'const' instead prefer-const + 97:13 error 'type' is never reassigned. Use 'const' instead prefer-const + 128:17 error 'group' is never reassigned. Use 'const' instead prefer-const + 140:13 error 'direct' is never reassigned. Use 'const' instead prefer-const + 141:18 error 'prop' is never reassigned. Use 'const' instead prefer-const + 142:22 error 'name' is never reassigned. Use 'const' instead prefer-const + 146:21 error 'found' is never reassigned. Use 'const' instead prefer-const + 177:13 error 'newTypes' is never reassigned. Use 'const' instead prefer-const + 178:18 error 'type' is never reassigned. Use 'const' instead prefer-const + 180:22 error 'source' is never reassigned. Use 'const' instead prefer-const + 181:21 error 'add' is never reassigned. Use 'const' instead prefer-const + 252:23 error 'prop' is never reassigned. Use 'const' instead prefer-const + 252:29 error 'value' is never reassigned. Use 'const' instead prefer-const + 258:13 error 'mounted' is never reassigned. Use 'const' instead prefer-const + 262:18 error 'ch' is never reassigned. Use 'const' instead prefer-const + 263:17 error 'str' is never reassigned. Use 'const' instead prefer-const + 283:29 warning 'mode' is assigned a value but never used @typescript-eslint/no-unused-vars + 284:13 error 'scope' is never reassigned. Use 'const' instead prefer-const + 285:13 error 'cursor' is never reassigned. Use 'const' instead prefer-const + 305:13 error 'node' is never reassigned. Use 'const' instead prefer-const + 315:13 error 'node' is never reassigned. Use 'const' instead prefer-const + 325:15 error 'enter' is never reassigned. Use 'const' instead prefer-const + 325:22 error 'leave' is never reassigned. Use 'const' instead prefer-const + 325:29 error 'from' is never reassigned. Use 'const' instead prefer-const + 325:39 error 'to' is never reassigned. Use 'const' instead prefer-const + 326:13 error 'mode' is never reassigned. Use 'const' instead prefer-const + 326:36 error 'anon' is never reassigned. Use 'const' instead prefer-const + 354:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 356:22 error 'id' is never reassigned. Use 'const' instead prefer-const + 407:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 416:13 error 'id' is never reassigned. Use 'const' instead prefer-const + 416:38 error 'endIndex' is never reassigned. Use 'const' instead prefer-const + 417:13 error 'type' is never reassigned. Use 'const' instead prefer-const + 423:13 error 'children' is never reassigned. Use 'const' instead prefer-const + 432:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 444:13 error 'b' is never reassigned. Use 'const' instead prefer-const + 445:13 error 'copy' is never reassigned. Use 'const' instead prefer-const + 449:17 error 'to' is never reassigned. Use 'const' instead prefer-const + 469:13 error 'last' is never reassigned. Use 'const' instead prefer-const + 488:13 error 'parent' is never reassigned. Use 'const' instead prefer-const + 493:9 error 'mode' is never reassigned. Use 'const' instead prefer-const + 501:13 error 'inner' is never reassigned. Use 'const' instead prefer-const + 522:21 error 'next' is never reassigned. Use 'const' instead prefer-const + 522:41 error 'start' is never reassigned. Use 'const' instead prefer-const + 528:25 error 'index' is never reassigned. Use 'const' instead prefer-const + 537:25 error 'inner' is never reassigned. Use 'const' instead prefer-const + 560:17 error 'rPos' is never reassigned. Use 'const' instead prefer-const + 561:24 error 'from' is never reassigned. Use 'const' instead prefer-const + 561:30 error 'to' is never reassigned. Use 'const' instead prefer-const + 595:13 error 'r' is never reassigned. Use 'const' instead prefer-const + 607:9 error 'cur' is never reassigned. Use 'const' instead prefer-const + 607:30 error 'result' is never reassigned. Use 'const' instead prefer-const + 654:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 655:13 error 'index' is never reassigned. Use 'const' instead prefer-const + 665:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 666:13 error 'index' is never reassigned. Use 'const' instead prefer-const + 676:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 677:13 error 'after' is never reassigned. Use 'const' instead prefer-const + 683:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 684:13 error 'parentStart' is never reassigned. Use 'const' instead prefer-const + 692:13 error 'children' is never reassigned. Use 'const' instead prefer-const + 692:28 error 'positions' is never reassigned. Use 'const' instead prefer-const + 693:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 694:13 error 'startI' is never reassigned. Use 'const' instead prefer-const + 694:38 error 'endI' is never reassigned. Use 'const' instead prefer-const + 696:17 error 'from' is never reassigned. Use 'const' instead prefer-const + 712:13 error 'r' is never reassigned. Use 'const' instead prefer-const + 760:15 error 'start' is never reassigned. Use 'const' instead prefer-const + 760:22 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 784:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 785:13 error 'index' is never reassigned. Use 'const' instead prefer-const + 816:13 error 'parent' is never reassigned. Use 'const' instead prefer-const + 826:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 826:39 error 'd' is never reassigned. Use 'const' instead prefer-const + 828:17 error 'parentStart' is never reassigned. Use 'const' instead prefer-const + 833:17 error 'after' is never reassigned. Use 'const' instead prefer-const + 844:30 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 863:25 error 'child' is never reassigned. Use 'const' instead prefer-const + 913:13 error 'cache' is never reassigned. Use 'const' instead prefer-const + 972:15 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 972:41 error 'types' is never reassigned. Use 'const' instead prefer-const + 976:17 error 'type' is never reassigned. Use 'const' instead prefer-const + 991:11 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 991:19 error 'nodeSet' is never reassigned. Use 'const' instead prefer-const + 991:28 error 'maxBufferLength' is never reassigned. Use 'const' instead prefer-const + 991:67 error 'reused' is never reassigned. Use 'const' instead prefer-const + 991:80 error 'minRepeatType' is never reassigned. Use 'const' instead prefer-const + 992:9 error 'cursor' is never reassigned. Use 'const' instead prefer-const + 993:9 error 'types' is never reassigned. Use 'const' instead prefer-const + 996:15 error 'id' is never reassigned. Use 'const' instead prefer-const + 996:19 error 'start' is never reassigned. Use 'const' instead prefer-const + 996:26 error 'end' is never reassigned. Use 'const' instead prefer-const + 996:31 error 'size' is never reassigned. Use 'const' instead prefer-const + 997:13 error 'lookAheadAtStart' is never reassigned. Use 'const' instead prefer-const + 1001:21 error 'node' is never reassigned. Use 'const' instead prefer-const + 1018:13 error 'type' is never reassigned. Use 'const' instead prefer-const + 1022:17 error 'data' is never reassigned. Use 'const' instead prefer-const + 1023:17 error 'endPos' is never reassigned. Use 'const' instead prefer-const + 1030:17 error 'endPos' is never reassigned. Use 'const' instead prefer-const + 1032:17 error 'localChildren' is never reassigned. Use 'const' instead prefer-const + 1032:37 error 'localPositions' is never reassigned. Use 'const' instead prefer-const + 1033:17 error 'localInRepeat' is never reassigned. Use 'const' instead prefer-const + 1053:21 error 'make' is never reassigned. Use 'const' instead prefer-const + 1065:32 error 'lastI' is never reassigned. Use 'const' instead prefer-const + 1076:13 error 'localChildren' is never reassigned. Use 'const' instead prefer-const + 1076:33 error 'localPositions' is never reassigned. Use 'const' instead prefer-const + 1086:17 error 'pair' is never reassigned. Use 'const' instead prefer-const + 1090:17 error 'pair' is never reassigned. Use 'const' instead prefer-const + 1102:13 error 'fork' is never reassigned. Use 'const' instead prefer-const + 1103:44 error 'minStart' is never reassigned. Use 'const' instead prefer-const + 1104:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 1106:17 error 'nodeSize' is never reassigned. Use 'const' instead prefer-const + 1119:17 error 'startPos' is never reassigned. Use 'const' instead prefer-const + 1123:17 error 'nodeStart' is never reassigned. Use 'const' instead prefer-const + 1149:15 error 'id' is never reassigned. Use 'const' instead prefer-const + 1149:19 error 'start' is never reassigned. Use 'const' instead prefer-const + 1149:26 error 'end' is never reassigned. Use 'const' instead prefer-const + 1149:31 error 'size' is never reassigned. Use 'const' instead prefer-const + 1152:17 error 'startIndex' is never reassigned. Use 'const' instead prefer-const + 1154:21 error 'endPos' is never reassigned. Use 'const' instead prefer-const + 1171:9 error 'children' is never reassigned. Use 'const' instead prefer-const + 1171:24 error 'positions' is never reassigned. Use 'const' instead prefer-const + 1174:9 error 'length' is never reassigned. Use 'const' instead prefer-const + 1184:18 error 'child' is never reassigned. Use 'const' instead prefer-const + 1213:9 error 'maxChild' is never reassigned. Use 'const' instead prefer-const + 1214:9 error 'localChildren' is never reassigned. Use 'const' instead prefer-const + 1214:29 error 'localPositions' is never reassigned. Use 'const' instead prefer-const + 1217:17 error 'groupFrom' is never reassigned. Use 'const' instead prefer-const + 1217:32 error 'groupStart' is never reassigned. Use 'const' instead prefer-const + 1220:21 error 'nextSize' is never reassigned. Use 'const' instead prefer-const + 1227:25 error 'only' is never reassigned. Use 'const' instead prefer-const + 1234:21 error 'length' is never reassigned. Use 'const' instead prefer-const + 1261:13 error 'parse' is never reassigned. Use 'const' instead prefer-const + 1263:17 error 'done' is never reassigned. Use 'const' instead prefer-const + 1337:13 error 'tag' is never reassigned. Use 'const' instead prefer-const + 1340:22 error 't' is never reassigned. Use 'const' instead prefer-const + 1357:13 error 'mod' is never reassigned. Use 'const' instead prefer-const + 1374:13 error 'exists' is never reassigned. Use 'const' instead prefer-const + 1377:13 error 'set' is never reassigned. Use 'const' instead prefer-const + 1377:23 error 'tag' is never reassigned. Use 'const' instead prefer-const + 1378:18 error 'm' is never reassigned. Use 'const' instead prefer-const + 1380:13 error 'configs' is never reassigned. Use 'const' instead prefer-const + 1381:18 error 'parent' is never reassigned. Use 'const' instead prefer-const + 1383:26 error 'config' is never reassigned. Use 'const' instead prefer-const + 1392:9 error 'sets' is never reassigned. Use 'const' instead prefer-const + 1452:9 error 'byName' is never reassigned. Use 'const' instead prefer-const + 1453:14 error 'prop' is never reassigned. Use 'const' instead prefer-const + 1457:18 error 'part' is never reassigned. Use 'const' instead prefer-const + 1459:21 error 'pieces' is never reassigned. Use 'const' instead prefer-const + 1465:25 error 'm' is never reassigned. Use 'const' instead prefer-const + 1472:25 error 'next' is never reassigned. Use 'const' instead prefer-const + 1481:21 error 'last' is never reassigned. Use 'const' instead prefer-const + 1481:47 error 'inner' is never reassigned. Use 'const' instead prefer-const + 1484:21 error 'rule' is never reassigned. Use 'const' instead prefer-const + 1517:9 error 'map' is never reassigned. Use 'const' instead prefer-const + 1518:14 error 'style' is never reassigned. Use 'const' instead prefer-const + 1522:22 error 'tag' is never reassigned. Use 'const' instead prefer-const + 1525:11 error 'scope' is never reassigned. Use 'const' instead prefer-const + 1525:18 error 'all' is never reassigned. Use 'const' instead prefer-const + 1529:22 error 'tag' is never reassigned. Use 'const' instead prefer-const + 1530:26 error 'sub' is never reassigned. Use 'const' instead prefer-const + 1531:25 error 'tagClass' is never reassigned. Use 'const' instead prefer-const + 1545:14 error 'highlighter' is never reassigned. Use 'const' instead prefer-const + 1546:13 error 'value' is never reassigned. Use 'const' instead prefer-const + 1571:9 error 'builder' is never reassigned. Use 'const' instead prefer-const + 1595:15 error 'type' is never reassigned. Use 'const' instead prefer-const + 1595:27 error 'start' is never reassigned. Use 'const' instead prefer-const + 1595:38 error 'end' is never reassigned. Use 'const' instead prefer-const + 1601:13 error 'rule' is never reassigned. Use 'const' instead prefer-const + 1602:13 error 'tagCls' is never reassigned. Use 'const' instead prefer-const + 1613:13 error 'mounted' is never reassigned. Use 'const' instead prefer-const + 1615:17 error 'inner' is never reassigned. Use 'const' instead prefer-const + 1616:17 error 'innerHighlighters' is never reassigned. Use 'const' instead prefer-const + 1617:17 error 'hasChild' is never reassigned. Use 'const' instead prefer-const + 1619:21 error 'next' is never reassigned. Use 'const' instead prefer-const + 1620:21 error 'nextPos' is never reassigned. Use 'const' instead prefer-const + 1621:21 error 'rangeFrom' is never reassigned. Use 'const' instead prefer-const + 1621:54 error 'rangeTo' is never reassigned. Use 'const' instead prefer-const + 2182:17 error 'themeKey' is never reassigned. Use 'const' instead prefer-const + 3844:21 error 'split' is never reassigned. Use 'const' instead prefer-const + 5350:13 error 'cx' is never reassigned. Use 'const' instead prefer-const + 5369:13 error 'depth' is never reassigned. Use 'const' instead prefer-const + 5369:65 error 'type' is never reassigned. Use 'const' instead prefer-const + 5370:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5371:13 error 'dPrec' is never reassigned. Use 'const' instead prefer-const + 5388:13 error 'base' is never reassigned. Use 'const' instead prefer-const + 5389:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 5389:74 error 'size' is never reassigned. Use 'const' instead prefer-const + 5404:13 error 'bufferBase' is never reassigned. Use 'const' instead prefer-const + 5404:59 error 'count' is never reassigned. Use 'const' instead prefer-const + 5407:17 error 'pos' is never reassigned. Use 'const' instead prefer-const + 5414:17 error 'baseStateID' is never reassigned. Use 'const' instead prefer-const + 5466:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 5471:17 error 'nextState' is never reassigned. Use 'const' instead prefer-const + 5471:39 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5505:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 5525:13 error 'buffer' is never reassigned. Use 'const' instead prefer-const + 5525:48 error 'base' is never reassigned. Use 'const' instead prefer-const + 5534:13 error 'isNode' is never reassigned. Use 'const' instead prefer-const + 5547:17 error 'action' is never reassigned. Use 'const' instead prefer-const + 5563:17 error 'best' is never reassigned. Use 'const' instead prefer-const + 5570:25 error 's' is never reassigned. Use 'const' instead prefer-const + 5576:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 5578:17 error 's' is never reassigned. Use 'const' instead prefer-const + 5581:17 error 'stack' is never reassigned. Use 'const' instead prefer-const + 5594:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5599:17 error 'depth' is never reassigned. Use 'const' instead prefer-const + 5599:69 error 'term' is never reassigned. Use 'const' instead prefer-const + 5600:17 error 'target' is never reassigned. Use 'const' instead prefer-const + 5602:21 error 'backup' is never reassigned. Use 'const' instead prefer-const + 5618:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5618:34 error 'seen' is never reassigned. Use 'const' instead prefer-const + 5619:13 error 'explore' is never reassigned. Use 'const' instead prefer-const + 5626:25 error 'rDepth' is never reassigned. Use 'const' instead prefer-const + 5628:29 error 'term' is never reassigned. Use 'const' instead prefer-const + 5628:75 error 'target' is never reassigned. Use 'const' instead prefer-const + 5634:25 error 'found' is never reassigned. Use 'const' instead prefer-const + 5658:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 5693:13 error 'last' is never reassigned. Use 'const' instead prefer-const + 5699:13 error 'last' is never reassigned. Use 'const' instead prefer-const + 5705:17 error 'newCx' is never reassigned. Use 'const' instead prefer-const + 5753:13 error 'term' is never reassigned. Use 'const' instead prefer-const + 5753:59 error 'depth' is never reassigned. Use 'const' instead prefer-const + 5763:13 error 'goto' is never reassigned. Use 'const' instead prefer-const + 5782:13 error 'next' is never reassigned. Use 'const' instead prefer-const + 5890:17 error 'next' is never reassigned. Use 'const' instead prefer-const + 5897:17 error 'next' is never reassigned. Use 'const' instead prefer-const + 5907:18 error 'range' is never reassigned. Use 'const' instead prefer-const + 5922:13 error 'idx' is never reassigned. Use 'const' instead prefer-const + 5928:17 error 'resolved' is never reassigned. Use 'const' instead prefer-const + 5953:13 error 'end' is never reassigned. Use 'const' instead prefer-const + 5961:19 error 'chunk' is never reassigned. Use 'const' instead prefer-const + 5961:26 error 'chunkPos' is never reassigned. Use 'const' instead prefer-const + 5971:17 error 'nextChunk' is never reassigned. Use 'const' instead prefer-const + 5972:17 error 'end' is never reassigned. Use 'const' instead prefer-const + 6049:18 error 'r' is never reassigned. Use 'const' instead prefer-const + 6065:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6078:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 6080:17 error 'atEof' is never reassigned. Use 'const' instead prefer-const + 6080:41 error 'nextPos' is never reassigned. Use 'const' instead prefer-const + 6137:20 error 'groupMask' is never reassigned. Use 'const' instead prefer-const + 6137:46 error 'dialect' is never reassigned. Use 'const' instead prefer-const + 6141:13 error 'accEnd' is never reassigned. Use 'const' instead prefer-const + 6147:21 error 'term' is never reassigned. Use 'const' instead prefer-const + 6155:13 error 'next' is never reassigned. Use 'const' instead prefer-const + 6163:17 error 'mid' is never reassigned. Use 'const' instead prefer-const + 6164:17 error 'index' is never reassigned. Use 'const' instead prefer-const + 6165:17 error 'from' is never reassigned. Use 'const' instead prefer-const + 6165:37 error 'to' is never reassigned. Use 'const' instead prefer-const + 6186:9 error 'iPrev' is never reassigned. Use 'const' instead prefer-const + 6191:82 error LOG is not listed as a dependency in turbo.json turbo/no-undeclared-env-vars + 6198:9 error 'cursor' is never reassigned. Use 'const' instead prefer-const + 6227:13 error 'fr' is never reassigned. Use 'const' instead prefer-const + 6254:17 error 'last' is never reassigned. Use 'const' instead prefer-const + 6259:17 error 'top' is never reassigned. Use 'const' instead prefer-const + 6259:41 error 'index' is never reassigned. Use 'const' instead prefer-const + 6266:17 error 'next' is never reassigned. Use 'const' instead prefer-const + 6267:17 error 'start' is never reassigned. Use 'const' instead prefer-const + 6276:25 error 'end' is never reassigned. Use 'const' instead prefer-const + 6278:29 error 'lookAhead' is never reassigned. Use 'const' instead prefer-const + 6303:45 warning '_' is defined but never used @typescript-eslint/no-unused-vars + 6308:15 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6308:37 error 'tokenizers' is never reassigned. Use 'const' instead prefer-const + 6309:13 error 'mask' is never reassigned. Use 'const' instead prefer-const + 6310:13 error 'context' is never reassigned. Use 'const' instead prefer-const + 6315:17 error 'tokenizer' is never reassigned. Use 'const' instead prefer-const + 6315:44 error 'token' is never reassigned. Use 'const' instead prefer-const + 6326:21 error 'startIndex' is never reassigned. Use 'const' instead prefer-const + 6353:13 error 'main' is never reassigned. Use 'const' instead prefer-const + 6353:39 error 'pos' is never reassigned. Use 'const' instead prefer-const + 6353:44 error 'p' is never reassigned. Use 'const' instead prefer-const + 6360:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 6363:19 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6366:25 error 'result' is never reassigned. Use 'const' instead prefer-const + 6392:15 error 'state' is never reassigned. Use 'const' instead prefer-const + 6392:34 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6392:56 error 'data' is never reassigned. Use 'const' instead prefer-const + 6447:15 error 'from' is never reassigned. Use 'const' instead prefer-const + 6462:13 error 'stacks' is never reassigned. Use 'const' instead prefer-const + 6462:35 error 'pos' is never reassigned. Use 'const' instead prefer-const + 6464:13 error 'newStacks' is never reassigned. Use 'const' instead prefer-const + 6474:18 error 's' is never reassigned. Use 'const' instead prefer-const + 6482:17 error 'stack' is never reassigned. Use 'const' instead prefer-const + 6497:25 error 'tok' is never reassigned. Use 'const' instead prefer-const + 6504:17 error 'finished' is never reassigned. Use 'const' instead prefer-const + 6516:17 error 'finished' is never reassigned. Use 'const' instead prefer-const + 6522:17 error 'maxRemaining' is never reassigned. Use 'const' instead prefer-const + 6536:21 error 'stack' is never reassigned. Use 'const' instead prefer-const + 6538:25 error 'other' is never reassigned. Use 'const' instead prefer-const + 6570:13 error 'start' is never reassigned. Use 'const' instead prefer-const + 6570:34 error 'parser' is never reassigned. Use 'const' instead prefer-const + 6571:13 error 'base' is never reassigned. Use 'const' instead prefer-const + 6575:17 error 'strictCx' is never reassigned. Use 'const' instead prefer-const + 6575:81 error 'cxHash' is never reassigned. Use 'const' instead prefer-const + 6577:21 error 'match' is never reassigned. Use 'const' instead prefer-const + 6586:21 error 'inner' is never reassigned. Use 'const' instead prefer-const + 6593:13 error 'defaultReduce' is never reassigned. Use 'const' instead prefer-const + 6603:13 error 'actions' is never reassigned. Use 'const' instead prefer-const + 6605:17 error 'action' is never reassigned. Use 'const' instead prefer-const + 6605:40 error 'term' is never reassigned. Use 'const' instead prefer-const + 6605:61 error 'end' is never reassigned. Use 'const' instead prefer-const + 6606:17 error 'last' is never reassigned. Use 'const' instead prefer-const + 6607:17 error 'localStack' is never reassigned. Use 'const' instead prefer-const + 6625:13 error 'pos' is never reassigned. Use 'const' instead prefer-const + 6638:17 error 'stack' is never reassigned. Use 'const' instead prefer-const + 6639:17 error 'base' is never reassigned. Use 'const' instead prefer-const + 6647:21 error 'done' is never reassigned. Use 'const' instead prefer-const + 6651:17 error 'force' is never reassigned. Use 'const' instead prefer-const + 6655:21 error 'done' is never reassigned. Use 'const' instead prefer-const + 6661:22 error 'insert' is never reassigned. Use 'const' instead prefer-const + 6703:13 error 'other' is never reassigned. Use 'const' instead prefer-const + 6753:13 error 'nodeNames' is never reassigned. Use 'const' instead prefer-const + 6757:13 error 'topTerms' is never reassigned. Use 'const' instead prefer-const + 6758:13 error 'nodeProps' is never reassigned. Use 'const' instead prefer-const + 6765:22 error 'propSpec' is never reassigned. Use 'const' instead prefer-const + 6770:25 error 'next' is never reassigned. Use 'const' instead prefer-const + 6775:29 error 'value' is never reassigned. Use 'const' instead prefer-const + 6794:13 error 'tokenArray' is never reassigned. Use 'const' instead prefer-const + 6817:18 error 'w' is never reassigned. Use 'const' instead prefer-const + 6823:13 error 'table' is never reassigned. Use 'const' instead prefer-const + 6827:17 error 'groupTag' is never reassigned. Use 'const' instead prefer-const + 6827:42 error 'last' is never reassigned. Use 'const' instead prefer-const + 6828:17 error 'target' is never reassigned. Use 'const' instead prefer-const + 6840:13 error 'data' is never reassigned. Use 'const' instead prefer-const + 6871:13 error 'deflt' is never reassigned. Use 'const' instead prefer-const + 6887:13 error 'result' is never reassigned. Use 'const' instead prefer-const + 6896:21 error 'value' is never reassigned. Use 'const' instead prefer-const + 6909:13 error 'copy' is never reassigned. Use 'const' instead prefer-const + 6913:17 error 'info' is never reassigned. Use 'const' instead prefer-const + 6920:21 error 'found' is never reassigned. Use 'const' instead prefer-const + 6926:21 error 'found' is never reassigned. Use 'const' instead prefer-const + 6929:21 error 'spec' is never reassigned. Use 'const' instead prefer-const + 6965:13 error 'prec' is never reassigned. Use 'const' instead prefer-const + 6970:13 error 'values' is never reassigned. Use 'const' instead prefer-const + 6970:50 error 'flags' is never reassigned. Use 'const' instead prefer-const + 6972:22 error 'part' is never reassigned. Use 'const' instead prefer-const + 6973:21 error 'id' is never reassigned. Use 'const' instead prefer-const + 6994:14 error 'stack' is never reassigned. Use 'const' instead prefer-const + 6995:13 error 'stopped' is never reassigned. Use 'const' instead prefer-const + 7005:13 error 'mask' is never reassigned. Use 'const' instead prefer-const + 7038:8 error 'next' is never reassigned. Use 'const' instead prefer-const + 7044:8 error 'next' is never reassigned. Use 'const' instead prefer-const + 7052:8 error 'next' is never reassigned. Use 'const' instead prefer-const + 7057:11 error 'mayPostfix' is never reassigned. Use 'const' instead prefer-const + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/abcdef.d.ts + 568:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/androidstudio.d.ts + 24:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/atomone.d.ts + 21:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/aura.d.ts + 21:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/bbedit.d.ts + 21:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/bespin.d.ts + 28:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/curves.d.ts + 21:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/darcula.d.ts + 27:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/dracula.d.ts + 568:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/duotone.d.ts + 21:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 42:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/eclipse.d.ts + 568:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/github.d.ts + 24:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 45:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/gruvbox.d.ts + 27:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 48:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/material.d.ts + 568:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 1136:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/noctisLilac.d.ts + 568:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/nord.d.ts + 568:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/okaidia.d.ts + 22:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/solarized.d.ts + 22:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 44:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/sublime.d.ts + 22:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/tokyoNight.d.ts + 568:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/tokyoNightDay.d.ts + 22:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/tokyoNightStorm.d.ts + 22:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/vscode.d.ts + 25:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/themes/xcode.d.ts + 25:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 47:66 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/dist/theming/utils.d.ts + 19:76 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/src/theming/utils.ts + 95:11 error 'themeKey' is never reassigned. Use 'const' instead prefer-const + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/src/tokenizer/ranges.ts + 100:22 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any + 283:14 error 'range' is never reassigned. Use 'const' instead prefer-const + +/Users/juliaortiz/Documents/Projects.nosync/curves/packages/code-motion/src/tokenizer/tokenizer.ts + 315:11 warning 'tokens' is assigned a value but never used @typescript-eslint/no-unused-vars + 358:11 error 'split' is never reassigned. Use 'const' instead prefer-const + +✖ 729 problems (724 errors, 5 warnings) + 569 errors and 0 warnings potentially fixable with the `--fix` option. + + ELIFECYCLE  Command failed with exit code 1. diff --git a/packages/code-motion/tsconfig.json b/packages/code-motion/tsconfig.json new file mode 100644 index 0000000..01eb68a --- /dev/null +++ b/packages/code-motion/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@coord/config/base.json", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["."], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/code-motion/vitest.config.ts b/packages/code-motion/vitest.config.ts new file mode 100644 index 0000000..08c58ba --- /dev/null +++ b/packages/code-motion/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineProject } from "vitest/config"; + +export default defineProject({ + test: { + alias: { + "@/": "src/", + }, + testTimeout: 1000, + }, +}); diff --git a/packages/tsconfig/base.json b/packages/config/base.json similarity index 71% rename from packages/tsconfig/base.json rename to packages/config/base.json index d72a9f3..0ea505d 100644 --- a/packages/tsconfig/base.json +++ b/packages/config/base.json @@ -2,19 +2,26 @@ "$schema": "https://json.schemastore.org/tsconfig", "display": "Default", "compilerOptions": { + "target": "ES2020", + "lib": ["es6", "dom", "es2022"], "composite": false, "declaration": true, - "declarationMap": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "inlineSources": false, "isolatedModules": true, + "declarationMap": true, "moduleResolution": "node", "noUnusedLocals": false, "noUnusedParameters": false, "preserveWatchOutput": true, "skipLibCheck": true, - "strict": true + "strict": true, + "allowJs": true, + "checkJs": true, + "resolveJsonModule": true, + "maxNodeModuleJsDepth": 1, + "noEmit": true }, "exclude": ["node_modules"] } diff --git a/packages/config/eslint.config.js b/packages/config/eslint.config.js new file mode 100644 index 0000000..f8d5c21 --- /dev/null +++ b/packages/config/eslint.config.js @@ -0,0 +1,69 @@ +import { FlatCompat } from "@eslint/eslintrc"; +import path from "path"; +import { fileURLToPath } from "url"; +import nextjs from "./next.eslint.js"; +// import next from "eslint-config-next"; +// mimic CommonJS variables -- not needed if using CommonJS +const __filename = fileURLToPath(import.meta.url); + +const __dirname = path.dirname(__filename); + +const compat = new FlatCompat({ + allConfig: undefined, + recommendedConfig: undefined, + baseDirectory: __dirname, +}); + +/** + * @type {Function} + * @param {object} config + * @returns {object[]} + */ +const config = compat.config.bind(compat); +const lintConfig = [ + ...config(nextjs), + ...config({ + extends: [ + // "next", + // nextjs, + "turbo", + "prettier", + "plugin:@typescript-eslint/recommended", + ], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + parserOptions: { + babelOptions: { + presets: ["next/babel"], + }, + }, + rules: { + "@next/next/no-html-link-for-pages": "off", + "@typescript-eslint/no-this-alias": "off", + "react-hooks/exhaustive-deps": "off", + "@typescript-eslint/ban-types": "off", + "prefer-const": [ + "error", + { + destructuring: "all", + ignoreReadBeforeAssign: false, + }, + ], + "@typescript-eslint/no-explicit-any": [ + "error", + { + ignoreRestArgs: true, + }, + ], + }, + }), + { + ignores: [ + "**/dist/**/*", + "**/node_modules/**/*", + "**/.next/**/*", + ], + }, +]; + +export default lintConfig; diff --git a/packages/config/next.eslint.js b/packages/config/next.eslint.js new file mode 100644 index 0000000..1a10eb3 --- /dev/null +++ b/packages/config/next.eslint.js @@ -0,0 +1,87 @@ +const config = { + extends: [ + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:@next/next/recommended", + ], + plugins: ["import", "react", "jsx-a11y"], + rules: { + "import/no-anonymous-default-export": "warn", + "react/no-unknown-property": "off", + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + "jsx-a11y/alt-text": [ + "warn", + { + elements: ["img"], + img: ["Image"], + }, + ], + "jsx-a11y/aria-props": "warn", + "jsx-a11y/aria-proptypes": "warn", + "jsx-a11y/aria-unsupported-elements": "warn", + "jsx-a11y/role-has-required-aria-props": + "warn", + "jsx-a11y/role-supports-aria-props": "warn", + "react/jsx-no-target-blank": "off", + }, + parser: "eslint-config-next/parser.js", + parserOptions: { + requireConfigFile: false, + sourceType: "module", + allowImportExportEverywhere: true, + babelOptions: { + presets: ["next/babel"], + caller: { + // Eslint supports top level await when a parser for it is included. We enable the parser by default for Babel. + supportsTopLevelAwait: true, + }, + }, + }, + overrides: [ + { + files: ["**/*.ts?(x)"], + parser: "@typescript-eslint/parser", + parserOptions: { + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + warnOnUnsupportedTypeScriptVersion: true, + }, + }, + ], + settings: { + react: { + version: "latest", + }, + "import/parsers": { + ["@typescript-eslint/parser"]: [ + ".ts", + ".mts", + ".cts", + ".tsx", + ".d.ts", + ], + }, + "import/resolver": { + ["eslint-import-resolver-node"]: { + extensions: [ + ".js", + ".jsx", + ".ts", + ".tsx", + ], + }, + ["eslint-import-resolver-typescript"]: { + alwaysTryTypes: true, + }, + }, + }, + env: { + browser: true, + node: true, + }, +}; + +export default config; diff --git a/packages/tsconfig/nextjs.json b/packages/config/nextjs.json similarity index 95% rename from packages/tsconfig/nextjs.json rename to packages/config/nextjs.json index fb719cc..17d7381 100644 --- a/packages/tsconfig/nextjs.json +++ b/packages/config/nextjs.json @@ -10,6 +10,7 @@ "incremental": true, "jsx": "preserve", "lib": ["dom", "dom.iterable", "esnext"], + "importHelpers": true, "module": "NodeNext", "noEmit": true, "resolveJsonModule": true, diff --git a/packages/config/package.json b/packages/config/package.json new file mode 100644 index 0000000..c5f103f --- /dev/null +++ b/packages/config/package.json @@ -0,0 +1,37 @@ +{ + "name": "@coord/config", + "version": "0.0.0", + "private": true, + "license": "MIT", + "type": "module", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@rollup/plugin-commonjs": "^25.0.3", + "@rollup/plugin-node-resolve": "^15.1.0", + "@rollup/plugin-typescript": "^11.1.2", + "@types/node": "20.2.5", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "autoprefixer": "10.4.14", + "eslint": "^8.45.0", + "eslint-config-next": "^13.4.12", + "eslint-config-prettier": "^8.8.0", + "eslint-config-turbo": "^1.10.9", + "eslint-plugin-react": "7.28.0", + "postcss-preset-env": "^9.0.0", + "rollup": "^3.26.3", + "rollup-plugin-dts": "^5.3.0", + "rollup-plugin-esbuild": "^5.0.0", + "rollup-plugin-postcss": "^4.0.2" + }, + "peerDependencies": { + "rollup": ">=3" + }, + "devDependencies": { + "rollup-plugin-typescript2": "^0.35.0", + "tslib": "^2.6.0", + "typescript": "^5.1.6" + } +} \ No newline at end of file diff --git a/packages/tsconfig/react-library.json b/packages/config/react-library.json similarity index 79% rename from packages/tsconfig/react-library.json rename to packages/config/react-library.json index 36b62be..5e6f3fc 100644 --- a/packages/tsconfig/react-library.json +++ b/packages/config/react-library.json @@ -4,8 +4,8 @@ "extends": "./base.json", "compilerOptions": { "jsx": "react-jsx", - "lib": ["ES2015", "DOM"], + "lib": ["ESNext", "DOM"], "module": "ESNext", - "target": "es6" + "strict": true } } diff --git a/packages/config/rollup/create-config.js b/packages/config/rollup/create-config.js new file mode 100644 index 0000000..ab72dee --- /dev/null +++ b/packages/config/rollup/create-config.js @@ -0,0 +1,98 @@ +import postcss from "rollup-plugin-postcss"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; +import postcssPresetEnv from "postcss-preset-env"; +import autoprefixer from "autoprefixer"; +import typescript from "rollup-plugin-typescript2"; + +/** + * @param {{ + * cssPrefix?: string; + * mapConfig?: (config: import('rollup').RollupOptions) => import('rollup').RollupOptions; + * rootDir?: string; + * } & import('rollup').RollupOptions} [config] + */ +export const createConfig = async ( + config = {} +) => { + const { + cssPrefix = "curvs", + mapConfig = (config) => config, + rootDir = process.cwd(), + input = "src/index.ts", + ...rest + } = config; + /** + * @type {{ + * dependencies?: Record; + * peerDependencies?: Record; + * devDependencies?: Record; + * }} + */ + const pkg = ( + await import(`${rootDir}/package.json`, { + assert: { type: "json" }, + }) + ).default; + const external = [ + ...Object.keys(pkg.dependencies ?? {}), + ...Object.keys(pkg.peerDependencies ?? {}), + "react/jsx-runtime", + "react", + "react-dom", + ]; + + return mapConfig({ + input, + output: [ + { + file: "dist/index.cjs", + format: "cjs", + sourcemap: true, + }, + { + file: "dist/index.js", + format: "es", + sourcemap: true, + }, + ], + context: "this", + plugins: [ + postcss({ + plugins: [ + postcssPresetEnv(), + autoprefixer(), + ], + extract: true, + namedExports: true, + modules: { + localsConvention: "camelCaseOnly", + dashedIdents: true, + /** @param {string} className */ + generateScopedName: (className) => + `${cssPrefix}-${className}`, + }, + minimize: true, + sourceMap: true, + }), + nodeResolve(), + commonjs(), + typescript({ + tsconfig: `${rootDir}/tsconfig.json`, + tsconfigOverride: { + include: Array.isArray(input) + ? input + : typeof input === "string" + ? [input] + : Object.values(input), + }, + exclude: [ + "**/*.test.ts", + "**/*.test.tsx", + ], + }), + ], + external, + ...rest, + }); +}; diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json new file mode 100644 index 0000000..38ebf9d --- /dev/null +++ b/packages/config/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "./base.json", + "compilerOptions": { + "module": "ESNext" + } +} diff --git a/packages/core/package.json b/packages/core/package.json index 4bdd75e..a4061f3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -19,20 +19,28 @@ "cjs", "esm" ], - "minify": true, + "minify": false, + "sourcemap": true, "entryPoints": [ "src/index.ts" ] }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + }, "devDependencies": { + "@types/lodash-es": "^4.17.7", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", - "react": "^17.0.2", - "tsconfig": "workspace:*", + "@coord/config": "workspace:*", "tsup": "^6.7.0", - "typescript": "^5.1.3" + "typescript": "^5.1.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21" }, "publishConfig": { "access": "public" - } + }, + "dependencies": {} } diff --git a/packages/core/src/components/accessibility.tsx b/packages/core/src/components/accessibility.tsx new file mode 100644 index 0000000..76328bb --- /dev/null +++ b/packages/core/src/components/accessibility.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +export const screenReaderOnly: React.CSSProperties = + { + clip: "rect(1px, 1px, 1px, 1px)", + overflow: "hidden", + position: "absolute", + width: 1, + height: 1, + whiteSpace: "nowrap", + + // https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe + }; + +export function ScreenReaderOnly( + props: React.HTMLAttributes +) { + return ( +
+ ); +} diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts new file mode 100644 index 0000000..d815d38 --- /dev/null +++ b/packages/core/src/components/index.ts @@ -0,0 +1 @@ +export * from "./accessibility"; diff --git a/packages/core/src/hooks/index.ts b/packages/core/src/hooks/index.ts new file mode 100644 index 0000000..f3eb3c1 --- /dev/null +++ b/packages/core/src/hooks/index.ts @@ -0,0 +1,3 @@ +export * from "./safe-hooks"; +export * from "./use-client-rect"; +export * from "./use-stopwatch"; diff --git a/packages/core/src/hooks/safe-hooks.ts b/packages/core/src/hooks/safe-hooks.ts new file mode 100644 index 0000000..08b20bf --- /dev/null +++ b/packages/core/src/hooks/safe-hooks.ts @@ -0,0 +1,87 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import React from "react"; +import { noop } from "../utils/noop"; +import { isFunction } from "../utils/is-function"; +import { makeId } from "../utils/make-id"; + +export const isServerComponent = + typeof React?.useState === "undefined"; + +export const useSafeState: typeof React.useState = + (initialState?: S | (() => S)) => { + if (isServerComponent) { + return [ + isFunction(initialState) + ? initialState() + : initialState, + noop, + ]; + } + + const [state, setState] = + React.useState(initialState); + + return [state, setState]; + }; + +export const useSafeEffect: typeof React.useEffect = + (effect, deps) => { + if (isServerComponent) { + return; + } + + React.useEffect(effect, deps); + }; + +export const useSafeLayoutEffect: typeof React.useEffect = + (effect, deps) => { + if (isServerComponent) { + return; + } + + React.useEffect(effect, deps); + }; + +export const useSafeRef: typeof React.useRef = < + T +>( + initialValue?: T | null +) => { + if (isServerComponent) { + return initialValue; + } + + return React.useRef(initialValue); +}; + +export const useSafeMemo: typeof React.useMemo = ( + factory, + deps +) => { + if (isServerComponent) { + return factory(); + } + + return React.useMemo(factory, deps); +}; + +export const useSafeCallback: typeof React.useCallback = + (callback, deps) => { + if (isServerComponent) { + return callback; + } + + return React.useCallback(callback, deps); + }; + +export const useSafeId = (prefix: string) => { + if (isServerComponent) { + return `${prefix}${makeId()}`; + } + return `${prefix}${React.useId().replace( + /\:/g, + "" + )}`; +}; + +// TODO: useSafeContext diff --git a/packages/core/src/hooks/use-client-rect.ts b/packages/core/src/hooks/use-client-rect.ts new file mode 100644 index 0000000..e3cc47c --- /dev/null +++ b/packages/core/src/hooks/use-client-rect.ts @@ -0,0 +1,31 @@ +import { + useSafeLayoutEffect, + useSafeRef, +} from "./safe-hooks"; + +export function useClientRect< + TRef extends HTMLElement = HTMLDivElement +>(fn: (rect: DOMRect) => void) { + const ref = useSafeRef(null); + + useSafeLayoutEffect(() => { + if (!ref.current) { + return; + } + const observer = new ResizeObserver(() => { + const newRect = + ref.current?.getBoundingClientRect(); + if (newRect) { + fn(newRect); + } + }); + + observer.observe(ref.current); + + return () => { + observer.disconnect(); + }; + }, [ref]); + + return ref; +} diff --git a/packages/core/src/hooks/use-stopwatch.ts b/packages/core/src/hooks/use-stopwatch.ts new file mode 100644 index 0000000..7e301f2 --- /dev/null +++ b/packages/core/src/hooks/use-stopwatch.ts @@ -0,0 +1,142 @@ +import { + useSafeEffect, + useSafeRef, +} from "./safe-hooks"; +import { EasingOptions, applyEasing } from ".."; + +/** + * useStopwatch custom React hook. + * + * @param {Function} fn - The callback function to be executed on each animation frame. + * @param {Object} config - The stopwatch configuration. + * @param {number} [config.from=0] - The start value of t. + * @param {number} [config.to=1] - The end value of t. + * @param {number} [config.duration=1000] - The update interval of the stopwatch in milliseconds. + * @param {boolean} [config.repeat=false] - If true, the stopwatch will loop from start to end. + * @param {boolean} [config.autoplay=false] - If true, the stopwatch will start automatically. + */ +export const useStopwatch = ( + fn: (t: number) => void, + config: { + from?: number; + to?: number; + durationInSeconds?: number; + repeat?: boolean; + autoplay?: boolean; + easing?: EasingOptions; + } = {} +) => { + const { + from = 0, + to = 1, + durationInSeconds = 1, + repeat = false, + autoplay = false, + easing = "easeInOutSine", + } = config; + const { current: state } = useSafeRef({ + isPlaying: false, + time: 0, + prevRequestTime: 0, + }); + const requestRef = useSafeRef< + number | undefined + >(undefined); + + const callback = (t: number) => { + state.time = Math.min(t, 1); + + const range = to - from; + fn( + from + + range * applyEasing(easing, state.time) + ); + }; + + // const duration = durationInSeconds * 1000; + const requestNextFrame = () => { + if (state.isPlaying === false) return; + + requestRef.current = + requestAnimationFrame(tick); + }; + /** + * Callback to be executed on each animation frame. + * + * @param {number} currentTime - The current time provided by requestAnimationFrame. + */ + + const tick = (currentTime: number) => { + if (durationInSeconds === 0) { + callback(1); + stop(); + return; + } + if (state.prevRequestTime === 0) { + state.prevRequestTime = currentTime; + } + const deltaTime = + (currentTime - state.prevRequestTime) / + 1000; + + state.prevRequestTime = currentTime; + if (deltaTime === 0) { + requestNextFrame(); + return; + } + const time = + state.time + deltaTime / durationInSeconds; + + if (time >= 1) { + if (repeat) { + callback(time % 1); + } else { + callback(1); + stop(); + } + } else { + callback(time); + } + + requestNextFrame(); + }; + + /** + * Starts or resumes the stopwatch. + * + * @param {number} t - The time to start at. + */ + const play = (t: number = state.time) => { + state.isPlaying = true; + state.time = t >= 1 ? 0 : t; + state.prevRequestTime = 0; + requestNextFrame(); + }; + + const pause = () => { + if (requestRef.current) { + cancelAnimationFrame(requestRef.current); + state.isPlaying = false; + } + }; + + const stop = () => { + pause(); + + state.time = 0; + }; + + useSafeEffect(() => { + if (autoplay) { + play(0); + } + return () => stop(); + }, []); + + return { + play, + stop, + pause, + isPlaying: state.isPlaying, + }; +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1c75624..1ab4d52 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,2 +1,5 @@ export * from "./math"; export * from "./utils"; +export * from "./types"; +export * from "./hooks"; +export * from "./components"; diff --git a/packages/core/src/math/transform.test.ts b/packages/core/src/math/transform.test.ts index 7a6aba4..7711acd 100644 --- a/packages/core/src/math/transform.test.ts +++ b/packages/core/src/math/transform.test.ts @@ -4,86 +4,165 @@ import { point } from "./vec2"; describe("Transform", () => { test("getPosition", () => { - const t = transform().setPosition({ x: 3, y: 6 }); - expect(t.getPosition()).toEqual({ x: 3, y: 6 }); + const t = transform().setPosition({ + x: 3, + y: 6, + }); + expect(t.getPosition()).toEqual({ + x: 3, + y: 6, + }); }); test("getRotation", () => { - const t = transform().setRotation(Math.PI / 4); - expect(t.getRotation()).toBeCloseTo(Math.PI / 4); + const t = transform().setRotation( + Math.PI / 4 + ); + expect(t.getRotation()).toBeCloseTo( + Math.PI / 4 + ); }); test("getScale", () => { - const t = transform().setScale({ x: 2, y: 3 }); + const t = transform().setScale({ + x: 2, + y: 3, + }); expect(t.getScale()).toEqual({ x: 2, y: 3 }); }); test("copy", () => { - const t1 = transform().setPosition({ x: 3, y: 6 }); + const t1 = transform().setPosition({ + x: 3, + y: 6, + }); const t2 = t1.copy(); expect(t1).not.toBe(t2); - expect(t1.getPosition()).toEqual(t2.getPosition()); + expect(t1.getPosition()).toEqual( + t2.getPosition() + ); }); test("setPosition", () => { - const t1 = transform().setPosition({ x: 3, y: 6 }); + const t1 = transform().setPosition({ + x: 3, + y: 6, + }); const t2 = t1.setPosition({ x: 4, y: 5 }); - expect(t1.getPosition()).toEqual({ x: 3, y: 6 }); - expect(t2.getPosition()).toEqual({ x: 4, y: 5 }); + expect(t1.getPosition()).toEqual({ + x: 3, + y: 6, + }); + expect(t2.getPosition()).toEqual({ + x: 4, + y: 5, + }); }); test("setRotation", () => { - const t1 = transform().setRotation(Math.PI / 4); + const t1 = transform().setRotation( + Math.PI / 4 + ); const t2 = t1.setRotation(Math.PI / 2); - expect(t1.getRotation()).toBeCloseTo(Math.PI / 4); - expect(t2.getRotation()).toBeCloseTo(Math.PI / 2); + expect(t1.getRotation()).toBeCloseTo( + Math.PI / 4 + ); + expect(t2.getRotation()).toBeCloseTo( + Math.PI / 2 + ); }); test("setScale", () => { - const t1 = transform().setScale({ x: 2, y: 3 }); + const t1 = transform().setScale({ + x: 2, + y: 3, + }); const t2 = t1.setScale({ x: 4, y: 5 }); expect(t1.getScale()).toEqual({ x: 2, y: 3 }); expect(t2.getScale()).toEqual({ x: 4, y: 5 }); }); test("scale", () => { - const t1 = transform().setScale({ x: 2, y: 3 }); + const t1 = transform().setScale({ + x: 2, + y: 3, + }); const t2 = t1.scale(2); - const t3 = t1.scale(2, 3); + const t3 = t1.scale([2, 3]); expect(t1.getScale()).toEqual({ x: 2, y: 3 }); expect(t2.getScale()).toEqual({ x: 4, y: 6 }); expect(t3.getScale()).toEqual({ x: 4, y: 9 }); }); test("scaleAboutOrigin", () => { - const t1 = transform().scaleAboutOrigin(point(0, 0), 2); - const t2 = transform().scaleAboutOrigin(point(5, 5), 2); - - expect(t1.applyTo(point(5, 5))).toEqual(point(10, 10)); - expect(t2.applyTo(point(5, 5))).toEqual(point(5, 5)); + const t1 = transform().scaleAboutOrigin( + point(0, 0), + 2 + ); + const t2 = transform().scaleAboutOrigin( + point(5, 5), + 2 + ); + + expect(t1.applyTo(point(5, 5))).toEqual( + point(10, 10) + ); + expect(t2.applyTo(point(5, 5))).toEqual( + point(5, 5) + ); }); test("translate", () => { - const t1 = transform().setPosition({ x: 3, y: 6 }); - const t2 = t1.translate(2, 3); - expect(t1.getPosition()).toEqual({ x: 3, y: 6 }); - expect(t2.getPosition()).toEqual({ x: 5, y: 9 }); + const t1 = transform().setPosition({ + x: 3, + y: 6, + }); + const t2 = t1.translate([2, 3]); + expect(t1.getPosition()).toEqual({ + x: 3, + y: 6, + }); + expect(t2.getPosition()).toEqual({ + x: 5, + y: 9, + }); }); test("rotate", () => { - const t1 = transform().setRotation(Math.PI / 4); + const t1 = transform().setRotation( + Math.PI / 4 + ); const t2 = t1.rotate(Math.PI / 2); - expect(t1.getRotation()).toBeCloseTo(Math.PI / 4); - expect(t2.getRotation()).toBeCloseTo(Math.PI / 4 + Math.PI / 2); + expect(t1.getRotation()).toBeCloseTo( + Math.PI / 4 + ); + expect(t2.getRotation()).toBeCloseTo( + Math.PI / 4 + Math.PI / 2 + ); }); test("lerp", () => { - const t1 = transform().setPosition({ x: 3, y: 6 }); - const t2 = transform().setPosition({ x: 6, y: 3 }); + const t1 = transform().setPosition({ + x: 3, + y: 6, + }); + const t2 = transform().setPosition({ + x: 6, + y: 3, + }); const t3 = t1.lerp(t2, 0.5); - expect(t1.getPosition()).toEqual({ x: 3, y: 6 }); - expect(t2.getPosition()).toEqual({ x: 6, y: 3 }); - expect(t3.getPosition()).toEqual({ x: 4.5, y: 4.5 }); + expect(t1.getPosition()).toEqual({ + x: 3, + y: 6, + }); + expect(t2.getPosition()).toEqual({ + x: 6, + y: 3, + }); + expect(t3.getPosition()).toEqual({ + x: 4.5, + y: 4.5, + }); }); test("applyTo", () => { @@ -95,7 +174,9 @@ describe("Transform", () => { }); test("invert", () => { - const t1 = transform().scale(2).translate(5, 5); + const t1 = transform() + .scale(2) + .translate([5, 5]); const t2 = t1.invert(); const p = point(5, 5); @@ -107,7 +188,9 @@ describe("Transform", () => { }); test("applyInverseTo", () => { - const t1 = transform().scale(2).translate(5, 5); + const t1 = transform() + .scale(2) + .translate([5, 5]); const p = point(5, 5); const q = t1.applyTo(p); diff --git a/packages/core/src/math/transform.ts b/packages/core/src/math/transform.ts index e342105..60358a4 100644 --- a/packages/core/src/math/transform.ts +++ b/packages/core/src/math/transform.ts @@ -1,4 +1,4 @@ -import { point, Vec2 } from "./vec2"; +import { point, Vec2, Vec2ish } from "./vec2"; export class Transform { _matrix: Mat3x3; @@ -15,6 +15,16 @@ export class Transform { static identity() { return new Transform(1, 0, 0, 1, 0, 0); } + static fromTransform(transform: Transform) { + return new Transform( + transform._matrix[0], + transform._matrix[1], + transform._matrix[3], + transform._matrix[4], + transform._matrix[2], + transform._matrix[5] + ); + } static fromMatrix(matrix: Mat3x3) { return new Transform( matrix[0], @@ -27,25 +37,34 @@ export class Transform { } getPosition() { - return point(this._matrix[2], this._matrix[5]); + return point( + this._matrix[2], + this._matrix[5] + ); } getRotation() { - return Math.atan2(this._matrix[1], this._matrix[0]); + return Math.atan2( + this._matrix[1], + this._matrix[0] + ); } getScale() { - return point(this._matrix[0], this._matrix[4]); + return point( + this._matrix[0], + this._matrix[4] + ); } copy() { return Transform.fromMatrix(this._matrix); } - setPosition(position: { x: number; y: number }) { + setPosition(position: Vec2ish) { return this.copy().setPositionSelf(position); } - setPositionSelf(position: { x: number; y: number }) { - const { x, y } = position; + setPositionSelf(position: Vec2ish) { + const { x, y } = Vec2.of(position); const { _matrix } = this; _matrix[2] = x; _matrix[5] = y; @@ -63,14 +82,19 @@ export class Transform { _matrix[1] = sin; _matrix[3] = -sin; _matrix[4] = cos; + return this; } - setScale(size: { x: number; y: number }) { - return this.copy().setScaleSelf(size); + setScale(factor: Vec2ish | number) { + return this.copy().setScaleSelf(factor); } - setScaleSelf(size: { x: number; y: number }) { - const { x, y } = size; + setScaleSelf(factor: Vec2ish | number) { + const { x, y } = Vec2.of( + typeof factor === "number" + ? [factor, factor] + : factor + ); const { _matrix } = this; _matrix[0] = x; _matrix[4] = y; @@ -80,14 +104,17 @@ export class Transform { /* * Scale the transform by the given factor. */ - scale(factor: number): this; - scale(x: number, y: number): this; - scale(x: number, y: number = x) { - return this.copy().scaleSelf(x, y); - } - scaleSelf(factor: number): this; - scaleSelf(x: number, y: number): this; - scaleSelf(x: number, y: number = x) { + + scale(factor: Vec2ish | number) { + return this.copy().scaleSelf(factor); + } + + scaleSelf(factor: Vec2ish | number) { + const { x, y } = Vec2.of( + typeof factor === "number" + ? [factor, factor] + : factor + ); const { _matrix } = this; _matrix[0] *= x; _matrix[1] *= x; @@ -96,9 +123,15 @@ export class Transform { return this; } scaleAboutOrigin(center: Vec2, factor: number) { - return this.copy().scaleAboutOriginSelf(center, factor); + return this.copy().scaleAboutOriginSelf( + center, + factor + ); } - scaleAboutOriginSelf(center: Vec2, factor: number) { + scaleAboutOriginSelf( + center: Vec2, + factor: number + ) { const { x, y } = center; const { _matrix } = this; @@ -119,11 +152,12 @@ export class Transform { return this; } - translate(x: number, y: number) { - return this.copy().translateSelf(x, y); + translate(offset: Vec2ish) { + return this.copy().translateSelf(offset); } - translateSelf(x: number, y: number) { + translateSelf(offset: Vec2ish) { + const { x, y } = Vec2.of(offset); const { _matrix } = this; _matrix[2] += x; _matrix[5] += y; @@ -154,7 +188,8 @@ export class Transform { lerpSelf(target: Transform, factor: number) { const { _matrix } = this; const [a, b, tx, c, d, ty] = _matrix; - const [ta, tb, ttx, tc, td, tty] = target._matrix; + const [ta, tb, ttx, tc, td, tty] = + target._matrix; _matrix[0] = a + (ta - a) * factor; _matrix[1] = b + (tb - b) * factor; _matrix[2] = tx + (ttx - tx) * factor; @@ -183,7 +218,10 @@ export class Transform { const { _matrix } = this; const [a, b, tx, c, d, ty] = _matrix; - return point(a * x + c * y + tx, b * x + d * y + ty); + return point( + a * x + c * y + tx, + b * x + d * y + ty + ); } applyInverseTo(p: Vec2) { @@ -200,7 +238,9 @@ export class Transform { toCss() { const [a, b, tx, c, d, ty] = this._matrix; // matrix(scaleX(), skewY(), skewX(), scaleY(), translateX(), translateY()) - return `matrix(${[a, c, b, d, tx, ty].join(",")})`; + return `matrix(${[a, c, b, d, tx, ty].join( + "," + )})`; } } export function transform(): Transform; @@ -213,7 +253,14 @@ export function transform( ty: number ): Transform; -export function transform(a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) { +export function transform( + a = 1, + b = 0, + c = 0, + d = 1, + tx = 0, + ty = 0 +) { return new Transform(a, b, c, d, tx, ty); } // prettier-ignore diff --git a/packages/core/src/math/vec2.ts b/packages/core/src/math/vec2.ts index c956df2..4165212 100644 --- a/packages/core/src/math/vec2.ts +++ b/packages/core/src/math/vec2.ts @@ -1,7 +1,13 @@ -export type Vec2ish = Vec2 | [number, number] | { x: number; y: number }; +export type Vec2ish = + | Vec2 + | [number, number] + | { x: number; y: number }; export class Vec2 { - constructor(public x: number, public y: number) {} + constructor( + public x: number, + public y: number + ) {} static of(pointish: Vec2ish) { if (pointish instanceof Vec2) { return pointish; @@ -20,10 +26,15 @@ export class Vec2 { return point(this.x, this.y); } isFinite() { - return Number.isFinite(this.x) && Number.isFinite(this.y); + return ( + Number.isFinite(this.x) && + Number.isFinite(this.y) + ); } isNaN() { - return Number.isNaN(this.x) || Number.isNaN(this.y); + return ( + Number.isNaN(this.x) || Number.isNaN(this.y) + ); } lerp(target: Vec2, t: number) { @@ -35,7 +46,10 @@ export class Vec2 { rotate(angle: number) { const cos = Math.cos(angle); const sin = Math.sin(angle); - return point(this.x * cos - this.y * sin, this.x * sin + this.y * cos); + return point( + this.x * cos - this.y * sin, + this.x * sin + this.y * cos + ); } angle() { return Math.atan2(this.y, this.x); @@ -51,41 +65,68 @@ export class Vec2 { return target.sub(this).length(); } length() { - return Math.sqrt(this.x * this.x + this.y * this.y); + return Math.sqrt( + this.x * this.x + this.y * this.y + ); } lengthSquared() { return this.x * this.x + this.y * this.y; } sub(target: Vec2) { - return point(this.x - target.x, this.y - target.y); + return point( + this.x - target.x, + this.y - target.y + ); } add(target: Vec2) { - return point(this.x + target.x, this.y + target.y); + return point( + this.x + target.x, + this.y + target.y + ); } mul(target: Vec2) { - return point(this.x * target.x, this.y * target.y); + return point( + this.x * target.x, + this.y * target.y + ); } div(target: Vec2) { - return point(this.x / target.x, this.y / target.y); + return point( + this.x / target.x, + this.y / target.y + ); } translate(x: number, y: number) { return point(this.x + x, this.y + y); } scale(factor: number) { - return point(this.x * factor, this.y * factor); + return point( + this.x * factor, + this.y * factor + ); } abs() { - return point(Math.abs(this.x), Math.abs(this.y)); + return point( + Math.abs(this.x), + Math.abs(this.y) + ); } normalize() { const length = this.length(); - return point(this.x / length, this.y / length); + return point( + this.x / length, + this.y / length + ); } normal() { return point(-this.y, this.x); } + + toString() { + return `[${this.x}, ${this.y}]`; + } } export function point(x: number, y: number) { diff --git a/packages/core/src/types/deep-paths.test-d.ts b/packages/core/src/types/deep-paths.test-d.ts deleted file mode 100644 index e4cd080..0000000 --- a/packages/core/src/types/deep-paths.test-d.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { assertType, describe } from "vitest"; -import { - InferMultiplePathValues, - InferPath, - InferPathValue, - InferPathValueTree, -} from "./deep-paths"; - -describe("types: deep paths", () => { - describe("InferPath", () => { - assertType< - InferPath<{ - foo: { - bar: { - baz: 1; - }; - }; - }> - >("" as "foo" | "foo.bar" | "foo.bar.baz"); - assertType< - InferPath<{ - foo: { - bar: { - baz: 1; - }; - }; - bar: { - baz: 1; - }; - }> - >("" as "foo" | "foo.bar" | "foo.bar.baz" | "bar" | "bar.baz"); - - assertType< - InferPath<{ - foo: - | { - bar: "hello"; - } - | { - baz: "world"; - }; - }> - >("" as "foo" | "foo.bar" | "foo.baz"); - - assertType< - InferPath<{ - foo: [ - { - bar: "hello"; - }, - { - baz: "world"; - } - ]; - }> - >("" as "foo" | "foo.0" | "foo.0.bar" | "foo.1" | "foo.1.baz"); - - assertType< - InferPath<{ - foo: ( - | { - bar: "hello"; - } - | { - baz: "world"; - } - )[]; - }> - >( - "" as "foo" | `foo.${number}` | `foo.${number}.bar` | `foo.${number}.baz` - ); - - assertType< - InferPath<{ - foo?: - | { - bar: 2; - } - | "hello"; - }> - >("" as "foo" | "foo.bar"); - }); - describe("InferPathValue", () => { - assertType< - InferPathValue< - { - foo: { - bar: { - baz: 1; - }; - }; - }, - "foo.bar.baz" - > - >(1); - - assertType< - InferPathValue< - { - foo: { - bar: { - baz: 1; - }; - }; - bar: { - baz: 2; - }; - }, - "foo.bar.baz" | "bar.baz" - > - >({} as 1 | 2); - - assertType< - InferPathValue< - { - foo?: { - bar: 1; - }; - }, - "foo.bar" - > - >({} as 1 | undefined); - - assertType< - InferPathValue< - { - foo: - | { - bar: 1; - } - | { - baz: 2; - }; - }, - "foo.bar" - > - >({} as 1 | undefined); - - assertType< - InferPathValue< - { - foo: [ - { - bar: 1; - }, - { - baz: 2; - } - ]; - }, - "foo.0.bar" | "foo.1.baz" - > - >({} as 1 | 2); - }); - describe("InferMultiplePathValues", () => { - assertType< - InferMultiplePathValues< - { - foo: { - bar: { - baz: 1; - }; - }; - bar: { - baz: 2; - }; - baz: { - hello: { - world: 3; - }; - }; - }, - ["foo.bar.baz", "bar.baz", "baz.hello.world"] - > - >([1, 2, 3]); - }); - - describe("InferPathValueTree", () => { - type testCase1 = { - foo: { - bar: { - baz: 1; - }; - }; - bar: { - baz: 2; - }; - baz: { - hello: { - world: 3; - }; - }; - }; - - assertType>( - {} as { - foo: testCase1["foo"]; - "foo.bar": testCase1["foo"]["bar"]; - "foo.bar.baz": testCase1["foo"]["bar"]["baz"]; - - bar: testCase1["bar"]; - "bar.baz": testCase1["bar"]["baz"]; - - baz: testCase1["baz"]; - "baz.hello": testCase1["baz"]["hello"]; - "baz.hello.world": testCase1["baz"]["hello"]["world"]; - } - ); - }); -}); diff --git a/packages/core/src/types/deep-paths.ts b/packages/core/src/types/deep-paths.ts deleted file mode 100644 index 8bca03a..0000000 --- a/packages/core/src/types/deep-paths.ts +++ /dev/null @@ -1,74 +0,0 @@ -type IsTuple> = number extends T["length"] - ? false - : true; - -type TupleKey = Exclude; - -type ArrayKey = number; - -export type AnythingButTupleOrRecord = T extends Record - ? T extends Array - ? IsTuple extends true - ? false - : true - : false - : true; - -export type ExtractBranches = TKey extends keyof any - ? Extract - : never; - -export type InferPathValueImpl< - TShape, - TPath, - TUndefined = false -> = TPath extends keyof TShape - ? TShape[TPath] | (TUndefined extends true ? undefined : never) - : TPath extends `${infer THead}.${infer TTail}` | `${infer THead}` - ? THead extends keyof TShape - ? InferPathValueImpl< - TShape[THead], - TTail, - TUndefined extends true ? true : AnythingButTupleOrRecord - > - : // The lines below basically normalizes the inputs and recursively call the type utility again - THead extends keyof ExtractBranches - ? InferPathValueImpl, TPath, true> - : never - : never; - -export type InferPathValue< - TShape extends Record, - TPath extends InferPath -> = InferPathValueImpl; - -type MakePaths = V extends Record - ? `${K}` | `${K}.${InferPathImpl}` - : `${K}`; - -export type InferPathImpl = T extends ReadonlyArray - ? IsTuple extends true - ? { - [K in TupleKey]-?: MakePaths; - }[TupleKey] - : MakePaths - : { - [K in keyof T]-?: MakePaths; - }[keyof T]; - -export type InferPath> = - InferPathImpl; - -export type InferMultiplePathValues< - TFieldValues extends Record, - TPath extends InferPath[] -> = { - [K in keyof TPath]: InferPathValueImpl< - TFieldValues, - TPath[K] & InferPath - >; -}; - -export type InferPathValueTree = { - [K in InferPathImpl]-?: InferPathValueImpl; -}; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 68f2f52..1b0e191 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1 +1,2 @@ -export * from "./deep-paths"; +export * from "./yielded-type"; +export * from "./utility-types"; diff --git a/packages/core/src/types/test-utils.ts b/packages/core/src/types/test-utils.ts index bffdccd..8cf61b9 100644 --- a/packages/core/src/types/test-utils.ts +++ b/packages/core/src/types/test-utils.ts @@ -1,7 +1,7 @@ export type Expect = T; -export type Equal = (() => T extends X ? 1 : 2) extends < - T ->() => T extends Y ? 1 : 2 +export type Equal = (() => T extends X + ? 1 + : 2) extends () => T extends Y ? 1 : 2 ? true : false; diff --git a/packages/core/src/types/utility-types.ts b/packages/core/src/types/utility-types.ts new file mode 100644 index 0000000..e4290a0 --- /dev/null +++ b/packages/core/src/types/utility-types.ts @@ -0,0 +1,5 @@ +export type Nullable = T | null | undefined; +export type PartialBy< + T, + K extends keyof T +> = Omit & Partial>; diff --git a/packages/core/src/types/yielded-type.ts b/packages/core/src/types/yielded-type.ts new file mode 100644 index 0000000..f34b929 --- /dev/null +++ b/packages/core/src/types/yielded-type.ts @@ -0,0 +1,11 @@ +export type YieldedType< + T extends + | ((...args: unknown[]) => Generator) + | Generator +> = T extends Generator + ? U + : T extends ( + ...args: unknown[] + ) => Generator + ? U + : never; diff --git a/packages/core/src/utils/clamp.ts b/packages/core/src/utils/clamp.ts new file mode 100644 index 0000000..a1962f1 --- /dev/null +++ b/packages/core/src/utils/clamp.ts @@ -0,0 +1,5 @@ +export const clamp = ( + value: number, + min: number, + max: number +) => Math.min(Math.max(value, min), max); diff --git a/packages/core/src/utils/easing-functions.ts b/packages/core/src/utils/easing-functions.ts new file mode 100644 index 0000000..0c2a3f1 --- /dev/null +++ b/packages/core/src/utils/easing-functions.ts @@ -0,0 +1,192 @@ +// All copied from +// https://github.com/ai/easings.net/blob/master/src/easings/easingsFunctions.ts + +const pow = Math.pow; +const sqrt = Math.sqrt; +const sin = Math.sin; +const cos = Math.cos; +const PI = Math.PI; +const c1 = 1.70158; +const c2 = c1 * 1.525; +const c3 = c1 + 1; +const c4 = (2 * PI) / 3; +const c5 = (2 * PI) / 4.5; + +const bounceOut: EasingFunction = function ( + x: number +) { + const n1 = 7.5625; + const d1 = 2.75; + + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + return n1 * (x -= 1.5 / d1) * x + 0.75; + } else if (x < 2.5 / d1) { + return n1 * (x -= 2.25 / d1) * x + 0.9375; + } else { + return n1 * (x -= 2.625 / d1) * x + 0.984375; + } +}; + +export const easingsFunctions = { + linear: (x: number) => x, + easeInQuad: function (x: number) { + return x * x; + }, + easeOutQuad: function (x: number) { + return 1 - (1 - x) * (1 - x); + }, + easeInOutQuad: function (x: number) { + return x < 0.5 + ? 2 * x * x + : 1 - pow(-2 * x + 2, 2) / 2; + }, + easeInCubic: function (x: number) { + return x * x * x; + }, + easeOutCubic: function (x: number) { + return 1 - pow(1 - x, 3); + }, + easeInOutCubic: function (x: number) { + return x < 0.5 + ? 4 * x * x * x + : 1 - pow(-2 * x + 2, 3) / 2; + }, + easeInQuart: function (x: number) { + return x * x * x * x; + }, + easeOutQuart: function (x: number) { + return 1 - pow(1 - x, 4); + }, + easeInOutQuart: function (x: number) { + return x < 0.5 + ? 8 * x * x * x * x + : 1 - pow(-2 * x + 2, 4) / 2; + }, + easeInQuint: function (x: number) { + return x * x * x * x * x; + }, + easeOutQuint: function (x: number) { + return 1 - pow(1 - x, 5); + }, + easeInOutQuint: function (x: number) { + return x < 0.5 + ? 16 * x * x * x * x * x + : 1 - pow(-2 * x + 2, 5) / 2; + }, + easeInSine: function (x: number) { + return 1 - cos((x * PI) / 2); + }, + easeOutSine: function (x: number) { + return sin((x * PI) / 2); + }, + easeInOutSine: function (x: number) { + return -(cos(PI * x) - 1) / 2; + }, + easeInExpo: function (x: number) { + return x === 0 ? 0 : pow(2, 10 * x - 10); + }, + easeOutExpo: function (x: number) { + return x === 1 ? 1 : 1 - pow(2, -10 * x); + }, + easeInOutExpo: function (x: number) { + return x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? pow(2, 20 * x - 10) / 2 + : (2 - pow(2, -20 * x + 10)) / 2; + }, + easeInCirc: function (x: number) { + return 1 - sqrt(1 - pow(x, 2)); + }, + easeOutCirc: function (x: number) { + return sqrt(1 - pow(x - 1, 2)); + }, + easeInOutCirc: function (x: number) { + return x < 0.5 + ? (1 - sqrt(1 - pow(2 * x, 2))) / 2 + : (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2; + }, + easeInBack: function (x: number) { + return c3 * x * x * x - c1 * x * x; + }, + easeOutBack: function (x: number) { + return ( + 1 + c3 * pow(x - 1, 3) + c1 * pow(x - 1, 2) + ); + }, + easeInOutBack: function (x: number) { + return x < 0.5 + ? (pow(2 * x, 2) * + ((c2 + 1) * 2 * x - c2)) / + 2 + : (pow(2 * x - 2, 2) * + ((c2 + 1) * (x * 2 - 2) + c2) + + 2) / + 2; + }, + easeInElastic: function (x: number) { + return x === 0 + ? 0 + : x === 1 + ? 1 + : -pow(2, 10 * x - 10) * + sin((x * 10 - 10.75) * c4); + }, + easeOutElastic: function (x: number) { + return x === 0 + ? 0 + : x === 1 + ? 1 + : pow(2, -10 * x) * + sin((x * 10 - 0.75) * c4) + + 1; + }, + easeInOutElastic: function (x: number) { + return x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? -( + pow(2, 20 * x - 10) * + sin((20 * x - 11.125) * c5) + ) / 2 + : (pow(2, -20 * x + 10) * + sin((20 * x - 11.125) * c5)) / + 2 + + 1; + }, + easeInBounce: function (x: number) { + return 1 - bounceOut(1 - x); + }, + easeOutBounce: bounceOut, + easeInOutBounce: function (x: number) { + return x < 0.5 + ? (1 - bounceOut(1 - 2 * x)) / 2 + : (1 + bounceOut(2 * x - 1)) / 2; + }, +}; + +export type EasingKeys = + keyof typeof easingsFunctions; +export type EasingFunction = ( + progress: number +) => number; +export type EasingOptions = + | EasingKeys + | EasingFunction; + +export const applyEasing = ( + easing: EasingOptions, + t: number +) => { + if (typeof easing === "function") { + return easing(t); + } else { + return easingsFunctions[easing](t); + } +}; diff --git a/packages/core/src/utils/formatDuration.ts b/packages/core/src/utils/formatDuration.ts new file mode 100644 index 0000000..832415c --- /dev/null +++ b/packages/core/src/utils/formatDuration.ts @@ -0,0 +1,19 @@ +export function formatDuration( + milliseconds: number, + showHours = false +) { + const seconds = Math.floor(milliseconds / 1000); + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor( + (seconds % 3600) / 60 + ); + const secondsLeft = Math.floor(seconds % 60); + + return ( + showHours + ? [hours, minutes, secondsLeft] + : [minutes, secondsLeft] + ) + .map((n) => n.toString().padStart(2, "0")) + .join(":"); +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 00a39d2..5129d88 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,2 +1,19 @@ export * from "./remap"; export * from "./lerp"; +export * from "./inverseLerp"; +export * from "./isObject"; +export * from "./easing-functions"; + +export * from "./clamp"; +export * from "./formatDuration"; +export * from "./random"; +export * from "./video-sizes"; +export * from "./is-function"; +export * from "./make-id"; +export * from "./noop"; +export * from "./is-undefined"; +export * from "./is-generator"; +export * from "./is-string"; +export * from "./is-number"; +export * from "./raise"; +export * from "./inline-styles"; diff --git a/packages/core/src/utils/inline-styles.ts b/packages/core/src/utils/inline-styles.ts new file mode 100644 index 0000000..a601b12 --- /dev/null +++ b/packages/core/src/utils/inline-styles.ts @@ -0,0 +1,11 @@ +import { Nullable } from "@/types"; +import { CSSProperties } from "react"; + +interface InlineStyles extends CSSProperties { + [key: `--${string}`]: string | number; +} +export function inlined( + ...args: Nullable[] +): CSSProperties { + return Object.assign({}, ...args); +} diff --git a/packages/core/src/utils/inverseLerp.ts b/packages/core/src/utils/inverseLerp.ts new file mode 100644 index 0000000..b5a5316 --- /dev/null +++ b/packages/core/src/utils/inverseLerp.ts @@ -0,0 +1,10 @@ +export const inverseLerp = ( + a: number, + b: number, + value: number +) => { + if (a === b) { + return value >= b ? 1 : 0; + } + return (value - a) / (b - a); +}; diff --git a/packages/core/src/utils/is-function.ts b/packages/core/src/utils/is-function.ts new file mode 100644 index 0000000..ddda565 --- /dev/null +++ b/packages/core/src/utils/is-function.ts @@ -0,0 +1,6 @@ +export function isFunction( + value: unknown + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): value is (...args: any[]) => any { + return typeof value === "function"; +} diff --git a/packages/core/src/utils/is-generator.ts b/packages/core/src/utils/is-generator.ts new file mode 100644 index 0000000..ff4f2ff --- /dev/null +++ b/packages/core/src/utils/is-generator.ts @@ -0,0 +1,17 @@ +export function isGenerator( + value: unknown +): value is Generator { + return ( + typeof value === "object" && + value !== null && + Symbol.iterator in value + ); +} +export const isGeneratorFunction = ( + value: unknown +): value is () => Generator => { + return ( + typeof value === "function" && + value.constructor.name === "GeneratorFunction" + ); +}; diff --git a/packages/core/src/utils/is-number.ts b/packages/core/src/utils/is-number.ts new file mode 100644 index 0000000..682e23b --- /dev/null +++ b/packages/core/src/utils/is-number.ts @@ -0,0 +1,5 @@ +export function isNumber( + value: unknown +): value is number { + return typeof value === "number"; +} diff --git a/packages/core/src/utils/is-string.ts b/packages/core/src/utils/is-string.ts new file mode 100644 index 0000000..73810ad --- /dev/null +++ b/packages/core/src/utils/is-string.ts @@ -0,0 +1,5 @@ +export function isString( + value: unknown +): value is string { + return typeof value === "string"; +} diff --git a/packages/core/src/utils/is-undefined.ts b/packages/core/src/utils/is-undefined.ts new file mode 100644 index 0000000..533d14e --- /dev/null +++ b/packages/core/src/utils/is-undefined.ts @@ -0,0 +1,5 @@ +export function isUndefined( + v: unknown +): v is undefined { + return typeof v === "undefined"; +} diff --git a/packages/core/src/utils/isObject.ts b/packages/core/src/utils/isObject.ts new file mode 100644 index 0000000..8738e41 --- /dev/null +++ b/packages/core/src/utils/isObject.ts @@ -0,0 +1,7 @@ +export function isObject( + value: unknown +): value is Record { + return ( + typeof value === "object" && value !== null + ); +} diff --git a/packages/core/src/utils/lerp.ts b/packages/core/src/utils/lerp.ts index b6faf11..0ffe369 100644 --- a/packages/core/src/utils/lerp.ts +++ b/packages/core/src/utils/lerp.ts @@ -1 +1,5 @@ -export const lerp = (a: number, b: number, t: number) => a + (b - a) * t; +export const lerp = ( + a: number, + b: number, + t: number +) => a + (b - a) * t; diff --git a/packages/core/src/utils/make-id.ts b/packages/core/src/utils/make-id.ts new file mode 100644 index 0000000..81436d6 --- /dev/null +++ b/packages/core/src/utils/make-id.ts @@ -0,0 +1,6 @@ +let _id = 0; +export function makeId( + prefix: T = "id-" as T +): `${T}${number}` { + return `${prefix}${_id++}`; +} diff --git a/packages/core/src/utils/noop.ts b/packages/core/src/utils/noop.ts new file mode 100644 index 0000000..e9a7552 --- /dev/null +++ b/packages/core/src/utils/noop.ts @@ -0,0 +1,3 @@ +export const noop = () => { + // do nothing +}; diff --git a/packages/core/src/utils/raise.ts b/packages/core/src/utils/raise.ts new file mode 100644 index 0000000..2160679 --- /dev/null +++ b/packages/core/src/utils/raise.ts @@ -0,0 +1,3 @@ +export function raise(message = "Error"): never { + throw new Error(message); +} diff --git a/packages/core/src/utils/random.ts b/packages/core/src/utils/random.ts new file mode 100644 index 0000000..fc88d64 --- /dev/null +++ b/packages/core/src/utils/random.ts @@ -0,0 +1,61 @@ +export class Random { + private _seed: number; + private readonly _initialSeed: number; + + constructor(seed: number | string) { + if (typeof seed === "string") { + seed = this.fromString(seed); + } + this._seed = seed; + this._initialSeed = seed; + } + + public next(): number { + // LCG parameters for Numerical Recipes + const m = Math.pow(2, 32); + const a = 1664525; + const c = 1013904223; + + this._seed = (a * this._seed + c) % m; + return this._seed / m; + } + + public range(min: number, max: number): number { + return min + this.next() * (max - min); + } + + public intRange( + min: number, + max: number + ): number { + return Math.floor(this.range(min, max)); + } + + public list( + n: number, + min = 0, + max = 1 + ): number[] { + const result: number[] = []; + for (let i = 0; i < n; i++) { + result.push(this.range(min, max)); + } + return result; + } + + public reset(): void { + this._seed = this._initialSeed; + } + + public chance = (chance = 0.5): boolean => { + return this.next() <= chance; + }; + + private fromString(str: string): number { + let seed = 0; + for (let i = 0; i < str.length; i++) { + seed += str.charCodeAt(i); + } + return seed; + } +} diff --git a/packages/core/src/utils/remap.ts b/packages/core/src/utils/remap.ts index a34e279..8063618 100644 --- a/packages/core/src/utils/remap.ts +++ b/packages/core/src/utils/remap.ts @@ -1,9 +1,13 @@ export const remap = ( - value: number, fromMin: number, fromMax: number, toMin: number, - toMax: number + toMax: number, + value: number ): number => { - return toMin + ((value - fromMin) * (toMax - toMin)) / (fromMax - fromMin); + return ( + toMin + + ((value - fromMin) * (toMax - toMin)) / + (fromMax - fromMin) + ); }; diff --git a/packages/core/src/utils/video-sizes.ts b/packages/core/src/utils/video-sizes.ts new file mode 100644 index 0000000..1325f39 --- /dev/null +++ b/packages/core/src/utils/video-sizes.ts @@ -0,0 +1,29 @@ +import { Vec2, Vec2ish } from "../math"; + +const fullHD = Vec2.of([1920, 1080]); +const HD = Vec2.of([1280, 720]); +const SD = Vec2.of([640, 480]); +const reels = Vec2.of([1080, 1920]); +const square = Vec2.of([1080, 1080]); + +export const videoSizes = { + fullHD: fullHD, + video: fullHD, + HD, + SD, + + reels, + square, +}; + +export type VideoSize = keyof typeof videoSizes; +export type VideoSizeish = VideoSize | Vec2ish; + +export const getVideoSize = ( + size: VideoSizeish +) => { + if (typeof size === "string") { + return videoSizes[size]; + } + return Vec2.of(size); +}; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index b535f9b..fb41b3e 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,8 +1,13 @@ { - "extends": "tsconfig/base.json", + "extends": "@coord/config/react-library.json", "compilerOptions": { + "jsx": "react", "noUncheckedIndexedAccess": true, - "noEmit": true + "noEmit": true, + "isolatedModules": false, + "paths": { + "@/*": ["./src/*"] + } }, "include": ["."], "exclude": ["dist", "build", "node_modules"] diff --git a/packages/docs/.gitignore b/packages/docs/.gitignore index 8f322f0..f448901 100644 --- a/packages/docs/.gitignore +++ b/packages/docs/.gitignore @@ -33,3 +33,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# contentlayer +.contentlayer diff --git a/packages/docs/.vscode/settings.json b/packages/docs/.vscode/settings.json new file mode 100644 index 0000000..84daf55 --- /dev/null +++ b/packages/docs/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "typescript.tsdk": "../../node_modules/.pnpm/typescript@5.1.3/node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} \ No newline at end of file diff --git a/packages/docs/eslint.config.mjs b/packages/docs/eslint.config.mjs new file mode 100644 index 0000000..2303a15 --- /dev/null +++ b/packages/docs/eslint.config.mjs @@ -0,0 +1,2 @@ +import custom from "@coord/config/eslint.config.js"; +export default custom; diff --git a/packages/docs/next.config.mjs b/packages/docs/next.config.mjs index 442ac5e..06ba52d 100644 --- a/packages/docs/next.config.mjs +++ b/packages/docs/next.config.mjs @@ -1,19 +1,28 @@ +import withMDX from "@next/mdx"; import rehypeMdxCodeProps from "rehype-mdx-code-props"; import remarkGfm from "remark-gfm"; import rehypeKatex from "rehype-katex"; import remarkMath from "remark-math"; + /** @type {import('next').NextConfig} */ const nextConfig = { + reactStrictMode: true, + swcMinify: true, experimental: { + serverActions: true, appDir: true, }, }; -import mdx from "@next/mdx"; -const withMDX = mdx({ + +/** @type {import('@next/mdx').NextMDXOptions} */ +const mdxConfig = { options: { - remarkPlugins: [remarkGfm, remarkMath], - rehypePlugins: [rehypeMdxCodeProps, rehypeKatex], - useDynamicImport: true, + remarkPlugins: [remarkMath, remarkGfm], + rehypePlugins: [ + rehypeMdxCodeProps, + rehypeKatex, + ], }, -}); -export default withMDX(nextConfig); +}; + +export default withMDX(mdxConfig)(nextConfig); diff --git a/packages/docs/package.json b/packages/docs/package.json index ee10b6d..553df21 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -6,30 +6,51 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "tsc", + "lint": "eslint --c ./eslint.config.mjs .", "clean": "rm -rf ./.next ./dist ./.turbo ./node_modules" }, "dependencies": { + "@coord/code-motion": "workspace:*", + "@coord/code-motion-react": "workspace:*", + "@coord/core": "workspace:*", + "@coord/graph": "workspace:*", + "@coord/motion": "workspace:*", + "@coord/motion-react": "workspace:*", + "@headlessui/react": "^1.7.15", + "@mdx-js/esbuild": "^2.3.0", "@mdx-js/loader": "^2.3.0", "@mdx-js/mdx": "^2.3.0", "@mdx-js/react": "^2.3.0", "@next/mdx": "^13.4.4", + "@primer/octicons-react": "^19.4.0", + "@stackblitz/sdk": "^1.9.0", "@types/mdx": "^2.0.5", + "contentlayer": "^0.3.4", + "date-fns": "^2.30.0", + "esbuild": "^0.18.11", + "eslint": "^8.44.0", + "eslint-config-next": "^13.4.8", + "fp-ts": "^2.16.0", "katex": "^0.16.7", - "next": "^13.4.4", + "lodash-es": "^4.17.21", + "next": "13.4.8", + "next-contentlayer": "^0.3.4", "next-mdx-remote": "^4.4.1", + "postcss-nesting": "^11.3.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-icons": "^4.9.0", + "react-icons": "^4.10.1", + "react-runner": "^1.0.3", "rehype-katex": "^6.0.3", "rehype-mdx-code-props": "^1.0.0", "remark-gfm": "^3.0.1", "remark-math": "^5.1.1" }, "devDependencies": { - "@coord/graph": "workspace:*", + "@coord/config": "workspace:*", "@tailwindcss/typography": "^0.5.9", "@total-typescript/ts-reset": "^0.4.2", + "@types/lodash-es": "^4.17.7", "@types/node": "20.2.5", "@types/react": "^18.2.8", "autoprefixer": "10.4.14", @@ -38,7 +59,6 @@ "react-live-runner": "^1.0.5", "tailwindcss": "3.3.2", "ts-dedent": "^2.2.0", - "tsconfig": "workspace:*", "typescript": "5.1.3" } } diff --git a/packages/docs/postcss.config.js b/packages/docs/postcss.config.js index 33ad091..9ec8aa4 100644 --- a/packages/docs/postcss.config.js +++ b/packages/docs/postcss.config.js @@ -1,6 +1,8 @@ module.exports = { plugins: { + "postcss-import": {}, + "tailwindcss/nesting": "postcss-nesting", tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/packages/docs/src/app/[[...slug]]/layout.tsx b/packages/docs/src/app/[[...slug]]/layout.tsx new file mode 100644 index 0000000..6cd1db2 --- /dev/null +++ b/packages/docs/src/app/[[...slug]]/layout.tsx @@ -0,0 +1,194 @@ +import "katex/dist/katex.min.css"; +import { Sidebar } from "@/components/navigation"; +import cn from "clsx"; +import { Header } from "@/components/Header"; +import { Footer } from "@/components/Footer"; +import { + Fragment, + PropsWithChildren, +} from "react"; +import { + RouteInfo, + RouteSummary, + getProjectRoutes, + getRouteInfo, +} from "@/utils/routes"; +import { + ChevronRightIcon, + ChevronLeftIcon, +} from "@primer/octicons-react"; +import Link from "next/link"; + +function Breadcrumb({ + path, +}: { + path: RouteSummary[]; +}) { + return ( +
+ {path.map((item, index) => { + const isLink = item.isPage; + const isActive = + index === path.length - 1; + + return ( + + {index > 0 && ( + + )} +
+ {isLink && !isActive ? ( + + {item.title} + + ) : ( + item.title + )} +
+
+ ); + })} +
+ ); +} + +function Body({ + routeInfo, + children, +}: PropsWithChildren<{ + routeInfo: RouteInfo; +}>) { + const { previous, next } = routeInfo; + return ( +
+
+ + +
+ {children} +
+
+ {previous && ( + + + {previous.title} + + )} + {next && ( + + {next.title} + + + )} +
+
+
+ ); +} + +const getAccentColor = (route: string) => { + if (route.startsWith("graph")) { + return "337 100%"; + } + if (route.startsWith("motion")) { + return "164 100%"; + } + if (route.startsWith("editor")) { + return "200 100%"; + } + + return "0 0%"; +}; +export default async function Layout( + props: PropsWithChildren<{ + params: { slug?: string[] }; + }> +) { + const route = + props.params.slug?.join("/") ?? ""; + const { sidebar, header } = + await getProjectRoutes(route); + const routeInfo = await getRouteInfo(route); + return ( + <> + +
+ + {routeInfo.layout !== "full" && ( +
+ + + {props.children} + +
+ )} + {routeInfo.layout === "full" && ( +
+ {props.children} +
+ )} +