Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/app/docs-infra/hooks/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ The `useCode` hook provides programmatic access to code display, editing, and tr
- File Navigation Issues
- Hash Behavior Not Working as Expected
- Exports:
- preloadCodeEditor
- Parameters: loader
- useCode
- Parameters: contentProps, opts
- useCodeComponents
- Types: CodeComponentsContext, UseCodeOpts, UseCodeResult
- Types: CodeComponentsContext, CodeEditorProps, UseCodeOpts, UseCodeResult

</details>

Expand Down
72 changes: 72 additions & 0 deletions docs/app/docs-infra/hooks/use-code/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@

## API Reference

### preloadCodeEditor

Warms the editor chunk ahead of first focus. Fails open.

**Parameters:**

| Parameter | Type | Default | Description |
| :-------- | :----------------- | :------ | :---------- |
| loader? | `CodeEditorLoader` | - | - |

**Return Value:**

```tsx
type ReturnValue = Promise<void>;
```

### useCode

**useCode Parameters:**
Expand Down Expand Up @@ -35,6 +51,40 @@ type ReturnValue = Partial<Components> | undefined;
type CodeComponentsContext = React.Context<Partial<Components> | undefined>;
```

### CodeEditorProps

A transparent textarea laid over an already-highlighted `<pre>`. The textarea
owns the text, so selection, undo/redo, IME, and spellcheck stay native; the
`<pre>` beneath it keeps painting, frames and all.

Nothing is highlighted here. An edit goes out through `setSource`, the host
re-parses, and the `<pre>` re-renders from the new tree — which is what keeps
emphasis frames, collapse placeholders, and the intersection-driven frame
hydration working while editing.

Indent and outdent go through `document.execCommand('insertText')` rather than
a direct value write, which is what keeps them on the browser's native undo
stack. The `inputType` vocabulary used to classify edits follows the approach
in Pierre's editor.

```typescript
type CodeEditorProps = {
/** Complete source, matching the text painted by the `<pre>` underneath. */
source: string;
/** Canonical file name reported back through `setSource`. */
fileName?: string;
language?: string;
/** Spaces inserted by Tab. */
tabSize?: number;
setSource: SetSource;
/** Fired on first focus, so the host can warm the live runtime. */
onActivate?: () => void;
/** Fired on Escape, so the host can move focus out. */
onExit?: () => void;
onReady?: (textarea: HTMLTextAreaElement | null) => void;
};
```

### UseCodeOpts

```typescript
Expand Down Expand Up @@ -285,3 +335,25 @@ type SourceEnhancer = (
fileName: string,
) => { data?: unknown | undefined } | Promise;
```

### SetSource

```typescript
type SetSource = (
source: string,
fileName?: string | undefined,
position?:
| {
position: number;
extent: number;
content: string;
line: number;
history?: 'undo' | 'redo' | undefined;
historyPivotLine?: number | undefined;
deletedFromLineStart?: boolean | undefined;
backward?: boolean | undefined;
}
| undefined,
preParsed?: Root | undefined,
) => void;
```
3 changes: 3 additions & 0 deletions packages/docs-infra/src/CodeProvider/CodeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
import type { ParseSourceAsync } from './createParseSourceWorkerClient';
import type { PreParsedCacheEntry } from '../CodeHighlighter/CodeHighlighterContext';
import type { EditingEngineLoader } from '../useCode/editingEngineCache';
import type { CodeEditorLoader } from '../useCode/codeEditorCache';
import type { CreateTransformedFiles } from '../useCode/TransformEngine';

// Type definitions for the heavy functions we're moving to context
Expand Down Expand Up @@ -125,6 +126,8 @@ export interface CodeContext {
* `editActivation: 'interaction'`.
*/
editingEngineLoader?: EditingEngineLoader;
/** Lazily loads the textarea editor. A read-only block never calls this. */
codeEditorLoader?: CodeEditorLoader;
}

export const CodeContext = React.createContext<CodeContext>({});
Expand Down
5 changes: 2 additions & 3 deletions packages/docs-infra/src/CodeProvider/CodeProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ describe('CodeProvider (eager)', () => {
await expect(ctx.loadCodeFallbackLoader!()).resolves.toBeTypeOf('function');
await expect(ctx.computeHastDeltasLoader!()).resolves.toBeTypeOf('function');
// The editing engine is bundled eagerly here, so its accessor resolves
// instantly to ONE module exposing both the contentEditable engine and the
// edit-time source-manipulation fns (proving they share a single chunk).
// instantly to the edit-time source-manipulation fns. The editing surface
// lives in its own chunk, reached through `codeEditorLoader`.
const editingModule = await ctx.editingEngineLoader!();
expect(editingModule.createEditableEngine).toBeTypeOf('function');
expect(editingModule.analyzeSource).toBeTypeOf('function');
expect(editingModule.toControlledCode).toBeTypeOf('function');
// The transform applier (jsondiffpatch path) resolves to `createTransformedFiles`.
Expand Down
5 changes: 5 additions & 0 deletions packages/docs-infra/src/CodeProvider/CodeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { loadIsomorphicCodeVariant } from '../pipeline/loadIsomorphicCodeVariant
import { computeHastDeltas } from '../pipeline/loadIsomorphicCodeVariant/computeHastDeltas';
import * as EditingEngine from '../useCode/EditingEngine';
import type { EditingEngineLoader } from '../useCode/editingEngineCache';
import type { CodeEditorLoader } from '../useCode/codeEditorCache';
import { createTransformedFiles } from '../useCode/TransformEngine';
// Eager: the emphasis enhancer is bundled so the synchronous editing
// re-enhancement path has it with no fetch (zero-latency invariant).
Expand All @@ -41,6 +42,9 @@ const loadVariantLoaderEager: LoadVariantLoader = () => Promise.resolve(loadIsom
const computeHastDeltasLoaderEager: ComputeHastDeltasLoader = () =>
Promise.resolve(computeHastDeltas);
const editingEngineLoaderEager: EditingEngineLoader = () => Promise.resolve(EditingEngine);
// The editor stays code-split even in the eager provider: bundling it would pull
// it into every page that renders a read-only code block.
const codeEditorLoaderEager: CodeEditorLoader = () => import('../useCode/CodeEditor');
const transformEngineLoaderEager: TransformEngineLoader = () =>
Promise.resolve(createTransformedFiles);

Expand Down Expand Up @@ -77,6 +81,7 @@ export function CodeProvider({
loadIsomorphicCodeVariantLoader: loadVariantLoaderEager,
computeHastDeltasLoader: computeHastDeltasLoaderEager,
editingEngineLoader: editingEngineLoaderEager,
codeEditorLoader: codeEditorLoaderEager,
transformEngineLoader: transformEngineLoaderEager,
defaultSourceEnhancers: [enhanceCodeEmphasis],
}),
Expand Down
10 changes: 6 additions & 4 deletions packages/docs-infra/src/CodeProvider/CodeProviderLazy.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ describe('CodeProviderLazy', () => {
await expect(ctx.loadIsomorphicCodeVariantLoader!()).resolves.toBeTypeOf('function');
await expect(ctx.loadCodeFallbackLoader!()).resolves.toBeTypeOf('function');
await expect(ctx.computeHastDeltasLoader!()).resolves.toBeTypeOf('function');
// The editing engine is the 4th lazy accessor (dynamic-import-backed), resolving
// to ONE module exposing both the contentEditable engine and the edit-time
// source-manipulation fns (proving they share a single dynamically-loaded chunk).
// The editing engine is the 4th lazy accessor (dynamic-import-backed),
// resolving to the edit-time source-manipulation fns.
const editingModule = await ctx.editingEngineLoader!();
expect(editingModule.createEditableEngine).toBeTypeOf('function');
expect(editingModule.analyzeSource).toBeTypeOf('function');
expect(editingModule.toControlledCode).toBeTypeOf('function');
// The editing surface is a separate chunk so a programmatic-only editor
// never pulls it in.
const editorModule = await ctx.codeEditorLoader!();
expect(editorModule.CodeEditor).toBeTypeOf('function');
// The transform applier (jsondiffpatch path) is dynamic-import-backed too,
// resolving to `createTransformedFiles`.
await expect(ctx.transformEngineLoader!()).resolves.toBeTypeOf('function');
Expand Down
3 changes: 3 additions & 0 deletions packages/docs-infra/src/CodeProvider/CodeProviderLazy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import { enhanceCodeEmphasisLazy } from '../pipeline/enhanceCodeEmphasis/enhance
import {
PRELOAD_KEY_COMPUTE_DELTAS,
PRELOAD_KEY_EDITING,
PRELOAD_KEY_CODE_EDITOR,
PRELOAD_KEY_LOAD_FALLBACK,
PRELOAD_KEY_LOAD_VARIANT,
PRELOAD_KEY_TRANSFORM_ENGINE,
computeHastDeltasFactory,
editingEngineFactory,
codeEditorFactory,
loadFallbackFactory,
loadVariantFactory,
transformEngineFactory,
Expand Down Expand Up @@ -121,6 +123,7 @@ function CodeProviderLazyInner({
loadIsomorphicCodeVariantLoader: () => preload(PRELOAD_KEY_LOAD_VARIANT, loadVariantFactory),
computeHastDeltasLoader: () => preload(PRELOAD_KEY_COMPUTE_DELTAS, computeHastDeltasFactory),
editingEngineLoader: () => preload(PRELOAD_KEY_EDITING, editingEngineFactory),
codeEditorLoader: () => preload(PRELOAD_KEY_CODE_EDITOR, codeEditorFactory),
transformEngineLoader: () => preload(PRELOAD_KEY_TRANSFORM_ENGINE, transformEngineFactory),
defaultSourceEnhancers: [enhanceCodeEmphasisLazy],
}),
Expand Down
5 changes: 5 additions & 0 deletions packages/docs-infra/src/CodeProvider/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { LoadFallbackCodeFn, LoadVariantFn, ComputeHastDeltasFn } from './CodeContext';
import type { EditingEngineModule } from '../useCode/editingEngineCache';
import type { CodeEditorModule } from '../useCode/codeEditorCache';
import type { CreateTransformedFiles } from '../useCode/TransformEngine';

/**
Expand Down Expand Up @@ -31,6 +32,7 @@ export const computeHastDeltasFactory = async (): Promise<ComputeHastDeltasFn> =
(await import('../pipeline/loadIsomorphicCodeVariant/computeHastDeltas')).computeHastDeltas;

export const PRELOAD_KEY_EDITING = 'docs-infra/editingEngine';
export const PRELOAD_KEY_CODE_EDITOR = 'docs-infra/codeEditor';

export const editingEngineFactory = async (): Promise<EditingEngineModule> =>
import('../useCode/EditingEngine');
Expand All @@ -39,3 +41,6 @@ export const PRELOAD_KEY_TRANSFORM_ENGINE = 'docs-infra/transformEngine';

export const transformEngineFactory = async (): Promise<CreateTransformedFiles> =>
(await import('../useCode/TransformEngine')).createTransformedFiles;

export const codeEditorFactory = async (): Promise<CodeEditorModule> =>
import('../useCode/CodeEditor');
2 changes: 2 additions & 0 deletions packages/docs-infra/src/CodeProvider/useCodeProviderValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
TransformEngineLoader,
} from './CodeContext';
import type { EditingEngineLoader } from '../useCode/editingEngineCache';
import type { CodeEditorLoader } from '../useCode/codeEditorCache';

/**
* The host-supplied source loaders. Identical for both providers (passed by the
Expand Down Expand Up @@ -47,6 +48,7 @@ export interface CodeProviderHeavyAccessors {
loadIsomorphicCodeVariantLoader: LoadVariantLoader;
computeHastDeltasLoader: ComputeHastDeltasLoader;
editingEngineLoader: EditingEngineLoader;
codeEditorLoader: CodeEditorLoader;
transformEngineLoader: TransformEngineLoader;
/**
* Provider-specific default source enhancers. The eager `CodeProvider` passes
Expand Down
Loading
Loading