@agent-native/core 0.41.1 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -56
- package/dist/action.d.ts +13 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +8 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +93 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/app-skill.d.ts +16 -0
- package/dist/cli/app-skill.d.ts.map +1 -1
- package/dist/cli/app-skill.js +33 -3
- package/dist/cli/app-skill.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +38 -16
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts +30 -3
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +180 -114
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +2 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +172 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/blocks/index.d.ts +11 -0
- package/dist/client/blocks/index.d.ts.map +1 -1
- package/dist/client/blocks/index.js +11 -0
- package/dist/client/blocks/index.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts +19 -0
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +6 -58
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/ApiEndpointBlock.js +116 -7
- package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
- package/dist/client/blocks/library/DataModelBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/DataModelBlock.js +75 -9
- package/dist/client/blocks/library/DataModelBlock.js.map +1 -1
- package/dist/client/blocks/library/DiffBlock.d.ts +1 -1
- package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/DiffBlock.js +265 -39
- package/dist/client/blocks/library/DiffBlock.js.map +1 -1
- package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/FileTreeBlock.js +27 -4
- package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
- package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
- package/dist/client/blocks/library/HighlightedCode.js +1 -1
- package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
- package/dist/client/blocks/library/JsonExplorerBlock.js +1 -1
- package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
- package/dist/client/blocks/library/MermaidBlock.js +1 -1
- package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +115 -0
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -0
- package/dist/client/blocks/library/annotation-rail.js +139 -0
- package/dist/client/blocks/library/annotation-rail.js.map +1 -0
- package/dist/client/blocks/library/api-endpoint.config.d.ts +31 -6
- package/dist/client/blocks/library/api-endpoint.config.d.ts.map +1 -1
- package/dist/client/blocks/library/api-endpoint.config.js +30 -6
- package/dist/client/blocks/library/api-endpoint.config.js.map +1 -1
- package/dist/client/blocks/library/callout.config.d.ts +29 -0
- package/dist/client/blocks/library/callout.config.d.ts.map +1 -0
- package/dist/client/blocks/library/callout.config.js +33 -0
- package/dist/client/blocks/library/callout.config.js.map +1 -0
- package/dist/client/blocks/library/callout.d.ts +20 -0
- package/dist/client/blocks/library/callout.d.ts.map +1 -0
- package/dist/client/blocks/library/callout.js +61 -0
- package/dist/client/blocks/library/callout.js.map +1 -0
- package/dist/client/blocks/library/checklist.d.ts.map +1 -1
- package/dist/client/blocks/library/checklist.js +3 -3
- package/dist/client/blocks/library/checklist.js.map +1 -1
- package/dist/client/blocks/library/code.d.ts.map +1 -1
- package/dist/client/blocks/library/code.js +32 -15
- package/dist/client/blocks/library/code.js.map +1 -1
- package/dist/client/blocks/library/columns.d.ts.map +1 -1
- package/dist/client/blocks/library/columns.js +56 -35
- package/dist/client/blocks/library/columns.js.map +1 -1
- package/dist/client/blocks/library/data-model.config.d.ts +17 -0
- package/dist/client/blocks/library/data-model.config.d.ts.map +1 -1
- package/dist/client/blocks/library/data-model.config.js +15 -0
- package/dist/client/blocks/library/data-model.config.js.map +1 -1
- package/dist/client/blocks/library/decision.config.d.ts +37 -0
- package/dist/client/blocks/library/decision.config.d.ts.map +1 -0
- package/dist/client/blocks/library/decision.config.js +32 -0
- package/dist/client/blocks/library/decision.config.js.map +1 -0
- package/dist/client/blocks/library/decision.d.ts +19 -0
- package/dist/client/blocks/library/decision.d.ts.map +1 -0
- package/dist/client/blocks/library/decision.js +119 -0
- package/dist/client/blocks/library/decision.js.map +1 -0
- package/dist/client/blocks/library/diagram.config.d.ts +64 -0
- package/dist/client/blocks/library/diagram.config.d.ts.map +1 -0
- package/dist/client/blocks/library/diagram.config.js +111 -0
- package/dist/client/blocks/library/diagram.config.js.map +1 -0
- package/dist/client/blocks/library/diagram.d.ts +16 -0
- package/dist/client/blocks/library/diagram.d.ts.map +1 -0
- package/dist/client/blocks/library/diagram.js +261 -0
- package/dist/client/blocks/library/diagram.js.map +1 -0
- package/dist/client/blocks/library/diff.config.d.ts +28 -6
- package/dist/client/blocks/library/diff.config.d.ts.map +1 -1
- package/dist/client/blocks/library/diff.config.js +30 -6
- package/dist/client/blocks/library/diff.config.js.map +1 -1
- package/dist/client/blocks/library/question-form.config.d.ts +69 -0
- package/dist/client/blocks/library/question-form.config.d.ts.map +1 -0
- package/dist/client/blocks/library/question-form.config.js +58 -0
- package/dist/client/blocks/library/question-form.config.js.map +1 -0
- package/dist/client/blocks/library/question-form.d.ts +20 -0
- package/dist/client/blocks/library/question-form.d.ts.map +1 -0
- package/dist/client/blocks/library/question-form.js +286 -0
- package/dist/client/blocks/library/question-form.js.map +1 -0
- package/dist/client/blocks/library/sanitize-html.d.ts +5 -0
- package/dist/client/blocks/library/sanitize-html.d.ts.map +1 -0
- package/dist/client/blocks/library/sanitize-html.js +240 -0
- package/dist/client/blocks/library/sanitize-html.js.map +1 -0
- package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
- package/dist/client/blocks/library/server-specs.js +59 -0
- package/dist/client/blocks/library/server-specs.js.map +1 -1
- package/dist/client/blocks/library/specs.d.ts.map +1 -1
- package/dist/client/blocks/library/specs.js +11 -0
- package/dist/client/blocks/library/specs.js.map +1 -1
- package/dist/client/blocks/library/tabs.d.ts.map +1 -1
- package/dist/client/blocks/library/tabs.js +12 -12
- package/dist/client/blocks/library/tabs.js.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.d.ts +260 -0
- package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -0
- package/dist/client/blocks/library/wireframe-kit.js +920 -0
- package/dist/client/blocks/library/wireframe-kit.js.map +1 -0
- package/dist/client/blocks/library/wireframe.config.d.ts +123 -0
- package/dist/client/blocks/library/wireframe.config.d.ts.map +1 -0
- package/dist/client/blocks/library/wireframe.config.js +294 -0
- package/dist/client/blocks/library/wireframe.config.js.map +1 -0
- package/dist/client/blocks/library/wireframe.d.ts +15 -0
- package/dist/client/blocks/library/wireframe.d.ts.map +1 -0
- package/dist/client/blocks/library/wireframe.js +206 -0
- package/dist/client/blocks/library/wireframe.js.map +1 -0
- package/dist/client/blocks/registry.d.ts +9 -0
- package/dist/client/blocks/registry.d.ts.map +1 -1
- package/dist/client/blocks/registry.js +12 -5
- package/dist/client/blocks/registry.js.map +1 -1
- package/dist/client/blocks/server.d.ts +1 -0
- package/dist/client/blocks/server.d.ts.map +1 -1
- package/dist/client/blocks/server.js +1 -0
- package/dist/client/blocks/server.js.map +1 -1
- package/dist/client/blocks/types.d.ts +10 -2
- package/dist/client/blocks/types.d.ts.map +1 -1
- package/dist/client/blocks/types.js.map +1 -1
- package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/DragHandle.js +152 -21
- package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
- package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +25 -1
- package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/RegistryBlockNode.js +29 -6
- package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.js +5 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +159 -12
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/store.d.ts +21 -0
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +33 -1
- package/dist/extensions/store.js.map +1 -1
- package/dist/server/recap-image-route.d.ts.map +1 -1
- package/dist/server/recap-image-route.js +12 -3
- package/dist/server/recap-image-route.js.map +1 -1
- package/dist/styles/agent-native.css +1 -0
- package/dist/styles/blocks.css +1380 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
- package/docs/content/plan-plugin.md +107 -0
- package/docs/content/pr-visual-recap.md +2 -2
- package/docs/content/skills-guide.md +8 -0
- package/docs/content/template-plan.md +94 -17
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
- package/docs/content/visual-plans.md +0 -80
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/client/blocks/types.ts"],"names":[],"mappings":"AAmWA,qEAAqE;AACrE,MAAM,UAAU,WAAW,CAAQ,IAAsB;IACvD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import type { FC } from \"react\";\nimport type { ZodType } from \"zod\";\n\n/**\n * Block-registry contract. A `BlockSpec` describes one document block end to end:\n * its data shape (`schema`), how it round-trips to MDX source (`mdx`), how it\n * renders read-only (`Read`) and how it is edited (`Edit`, or an auto-generated\n * schema-driven editor when omitted), where it can be placed (`placement`), and\n * metadata for menus / agent schema export.\n *\n * The registry runs ALONGSIDE existing per-block code (the plan `PlanBlockView`\n * switch + `serializeBlock`/`parseBlock`). Renderers check the registry first;\n * unregistered block types fall through to the legacy code path unchanged. The\n * MDX `tag` and attribute shape for a converted block MUST match the historical\n * encoding (e.g. `<Callout tone>…body…</Callout>`) so stored `.mdx` files still\n * parse byte-compatibly.\n */\n\n/** Where a block can be placed in a document. */\nexport type BlockPlacement = \"block\" | \"inline\";\n\n/**\n * A serialized MDX/NFM attribute value before the shared `prop()` encoder runs.\n * `prop()` decides string-vs-JSON encoding; this is just the value domain.\n */\nexport type MdxAttrValue =\n | string\n | number\n | boolean\n | unknown[]\n | Record<string, unknown>;\n\n/**\n * Type-narrowed reader over the resolved MDX attributes of a parsed block node.\n * The values are already estree/JSON-resolved by the shared attribute reader\n * (the same engine `plan-mdx.ts` uses), so a spec's `fromAttrs` never touches\n * the AST directly.\n */\nexport interface BlockAttrReader {\n string(name: string): string | undefined;\n number(name: string): number | undefined;\n bool(name: string): boolean | undefined;\n array<T = unknown>(name: string): T[] | undefined;\n object<T = unknown>(name: string): T | undefined;\n raw(name: string): unknown;\n}\n\n/**\n * Maps a block's validated data to/from its MDX component representation.\n * `tag` is the JSX component name in source (e.g. \"Callout\"). It MUST match the\n * historical name in `plan-mdx.ts` `BLOCK_COMPONENTS` / stored `.mdx` files or\n * existing plans break.\n */\nexport interface BlockMdxConfig<TData> {\n /** JSX component name in MDX source. Stable contract — never rename. */\n tag: string;\n /**\n * Encode `data` → a flat attribute bag. The registry serializer runs each\n * value through the shared `prop()` encoder (string-vs-JSON heuristic) and\n * preserves insertion order, so write the keys in the exact historical order.\n * Return `undefined` for a key (or omit it) to drop the attribute. When\n * `childrenField` is set, that field is excluded from the attribute bag.\n */\n toAttrs: (data: TData) => Record<string, MdxAttrValue | undefined>;\n /**\n * Decode resolved attributes (+ optional children markdown) → data. Must\n * tolerate missing/partial attributes for backward-compat (mirror today's\n * `?? []` / `?? \"\"` defaults).\n */\n fromAttrs: (attrs: BlockAttrReader, children: string) => TData;\n /**\n * When set, this data field is a markdown string serialized as MDX *children*\n * between the open/close tags (prose-bearing blocks: rich-text, callout)\n * rather than as a prop — so the body survives as real, inline-editable MDX\n * prose in source.\n */\n childrenField?: keyof TData & string;\n /**\n * Opt-in custom children serializer for blocks whose internals are nested MDX\n * components rather than a single markdown string (e.g. wireframe → Screen/kit\n * primitives). When present it overrides `childrenField`. `serializeChildren`\n * returns the raw inner MDX; `parseChildren` receives the child MDX AST nodes.\n */\n serializeChildren?: (data: TData) => string;\n parseChildren?: (childNodes: unknown[], idContext: string) => Partial<TData>;\n}\n\n/**\n * App-injected capabilities. Core blocks stay app-agnostic by taking these\n * rather than importing app services — mirroring `createImageExtension`'s\n * `onImageUpload` injection. Provided via `BlockRegistryProvider`.\n */\nexport interface BlockRenderContext {\n /** Markdown dialect for the auto-editor's rich-text field. */\n dialect?: \"gfm\" | \"nfm\";\n /** Resolve an asset id → displayable URL. */\n resolveAssetSrc?: (assetId: string) => string | undefined;\n /** Open the shared asset picker (returns the chosen asset). */\n pickAsset?: () => Promise<{ assetId: string; url?: string } | null>;\n /** Upload a local file, returns a hosted URL. */\n uploadFile?: (file: File) => Promise<{ url: string; assetId?: string }>;\n /** Call an app action by name (for blocks that fetch live data). */\n callAction?: (name: string, args: unknown) => Promise<unknown>;\n /** Sanitizer for HTML-bearing blocks. Provided by the app/core. */\n sanitizeHtml?: (html: string, css?: string) => string;\n /**\n * Render a markdown string with the app's read-only markdown renderer. Lets a\n * core block (whose `Read` lives in core) defer prose rendering to the app's\n * markdown reader (e.g. the plan `PlanMarkdownReader`) without importing it.\n */\n renderMarkdown?: (\n markdown: string,\n options?: { className?: string },\n ) => React.ReactNode;\n /**\n * Render an inline, editable rich-markdown field. The auto-editor calls this\n * for a `markdown()`-tagged field so the app owns the editor wiring (collab,\n * autosave debounce, dialect) rather than core hardcoding it.\n */\n renderMarkdownEditor?: (props: {\n value: string;\n onChange: (next: string) => void;\n editable: boolean;\n blockId?: string;\n className?: string;\n ariaLabel?: string;\n }) => React.ReactNode;\n /**\n * Render an app-owned \"Edit with AI\" affordance for a focused/editable block\n * field. Core block editors pass the current field value and nearby companion\n * fields; the host app decides how to collect the prompt and route it to the\n * agent sidebar. This keeps reusable core blocks from importing app-specific\n * popover/composer code while still exposing a generic AI edit hook.\n */\n renderAiFieldAction?: (props: BlockAiFieldActionProps) => React.ReactNode;\n /**\n * Render a nested child block through the app's own block dispatcher. Container\n * blocks whose `Read`/`Edit` live in core (e.g. tabs) call this to render each\n * child so the recursion keeps flowing through the SAME app renderer the\n * top-level document uses — registered children render via their spec, and\n * unregistered (not-yet-converted) children still fall through the app's legacy\n * switch. This is the coexistence seam: a core container never has to know\n * about app-specific child block types. Returns `null`/`undefined` when no\n * dispatcher is wired (read-only/SSR-only contexts can omit it).\n */\n renderBlock?: (props: {\n block: NestedBlock;\n /** Commit a replacement for this child block (edit mode only). */\n onChange?: (next: NestedBlock) => void;\n /** Whether the parent container is being edited. */\n editing?: boolean;\n /** Tighten embedded visuals in dense contexts (e.g. tab panes). */\n compactVisuals?: boolean;\n }) => React.ReactNode;\n /**\n * Render a nested editable block list through the host app's document editor.\n * Container blocks such as columns call this for each editable region so slash\n * commands, nested structured blocks, and ordinary prose behave like the\n * top-level document while the container still persists its normalized runtime\n * data. Source adapters may still expose a human-friendly nested MDX form\n * (for example `<Columns><Column>markdown</Column></Columns>`) and normalize it\n * into these block arrays at runtime.\n */\n renderBlocksEditor?: (props: {\n blocks: NestedBlock[];\n onChange: (blocks: NestedBlock[]) => void;\n editable: boolean;\n containerBlockId: string;\n regionId: string;\n regionLabel?: string;\n /** Tighten embedded visuals in dense regions such as tab panes. */\n compactVisuals?: boolean;\n }) => React.ReactNode;\n /**\n * Wrap a block's edit form in an app-provided \"panel\" surface (e.g. a shadcn\n * Popover anchored to the corner edit button) for `editSurface: \"panel\"`\n * blocks. Core renders the rendered `Read` view plus a corner trigger button\n * and the form, then hands them here so the app owns the overlay primitive\n * (core stays shadcn-free, mirroring `renderMarkdownEditor`). When omitted, a\n * panel-mode block falls back to inline editing. `title` is the block label.\n */\n renderEditSurface?: (props: {\n title: string;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n trigger: React.ReactNode;\n children: React.ReactNode;\n /** Metadata for host-provided contextual controls such as \"Edit with AI\". */\n blockId?: string;\n blockType?: string;\n blockTitle?: string;\n blockSummary?: string;\n blockData?: unknown;\n }) => React.ReactNode;\n}\n\nexport interface BlockAiFieldActionProps {\n blockId: string;\n blockType: string;\n blockTitle?: string;\n blockSummary?: string;\n fieldLabel: string;\n fieldValue: string;\n draftScope: string;\n disabled?: boolean;\n /**\n * Human-readable instructions for the host agent prompt. Mention how to patch\n * the block and which sibling fields should be preserved.\n */\n instructions: string;\n companionFields?: Array<{\n label: string;\n value: string;\n language?: string;\n }>;\n}\n\n/**\n * The minimal shape of a nested child block passed to {@link\n * BlockRenderContext.renderBlock}. It mirrors the app's block union loosely (the\n * app casts it back to its own block type) — a discriminating `type`, a stable\n * `id`, optional heading/summary, and the type-specific `data`.\n */\nexport interface NestedBlock {\n type: string;\n id: string;\n title?: string;\n summary?: string;\n data: unknown;\n [key: string]: unknown;\n}\n\nexport interface BlockContainerRegion {\n id: string;\n label?: string;\n blocks: NestedBlock[];\n}\n\nexport interface BlockContainerSpec<TData> {\n regions: (data: TData) => BlockContainerRegion[];\n updateRegion: (data: TData, regionId: string, blocks: NestedBlock[]) => TData;\n addRegion?: (data: TData, afterRegionId?: string) => TData;\n removeRegion?: (data: TData, regionId: string) => TData;\n reorderRegion?: (\n data: TData,\n fromRegionId: string,\n toRegionId: string,\n ) => TData;\n}\n\nexport type BlockDataChangeMeta = {\n containerRegion?: {\n regionId: string;\n blocks: NestedBlock[];\n };\n};\n\n/** Props passed to a block's read-only renderer. */\nexport interface BlockReadProps<TData> {\n data: TData;\n /** Stable block id (for anchors, comment targeting, source patches). */\n blockId: string;\n /** Block heading, when present. */\n title?: string;\n /** Block trailing summary, when present. */\n summary?: string;\n /** Injected app capabilities. */\n ctx: BlockRenderContext;\n}\n\n/** Props passed to a block's editor (custom or schema-generated). */\nexport interface BlockEditProps<TData> {\n data: TData;\n onChange: (next: TData, meta?: BlockDataChangeMeta) => void;\n editable: boolean;\n blockId: string;\n title?: string;\n summary?: string;\n /** Injected app capabilities. */\n ctx: BlockRenderContext;\n}\n\nexport interface BlockSpec<TData = unknown> {\n /** Discriminator. Equals the runtime block `type`. */\n type: string;\n /** Zod schema for `data`. Drives validation AND the schema-auto-editor. */\n schema: ZodType<TData>;\n /** MDX round-trip config. */\n mdx: BlockMdxConfig<TData>;\n /** Read-only renderer (replaces a `PlanBlockView` switch branch / NodeView). */\n Read: FC<BlockReadProps<TData>>;\n /**\n * Optional editor. When omitted, the registry renders the schema-driven\n * `SchemaBlockEditor` generated from `schema`. Supply for full control\n * (wireframe canvas, diagram editor).\n */\n Edit?: FC<BlockEditProps<TData>>;\n /** Allowed placements: `[\"block\"]`, `[\"inline\"]`, or both. */\n placement: BlockPlacement[];\n /**\n * When `true`, this block's data maps to a Notion-Flavored-Markdown (NFM)\n * analog and therefore round-trips into a Notion page. Apps can derive\n * registry-backed Notion allowlists with\n * {@link BlockRegistry.notionCompatibleTypes} instead of hand-maintaining\n * per-app sets. Set it on registry-atom blocks with an NFM counterpart\n * (checklist, table); leave it `false`/undefined on dev-doc blocks\n * (api-endpoint, openapi-spec, data-model, diff, file-tree, json-explorer,\n * annotated-code, mermaid, custom-html, tabs, code-tabs) and visual/plan-only\n * blocks (wireframe, diagram). Prose blocks that aren't registry atoms\n * (rich-text, callout) carry their NFM analog through the prose path, not this\n * flag.\n */\n notionCompatible?: boolean;\n /**\n * How the block is edited in a `block`-placed document:\n * - `\"inline\"` — the `Edit`/auto-form renders in place for direct\n * manipulation of authored content (prose, checklist text, table cells,\n * code bodies). Schema-ish metadata such as tone/type, tab labels,\n * language, density, or structural settings should still be tucked behind a\n * contextual edit/settings affordance inside the custom `Edit`.\n * - `\"panel\"` — the block shows its rendered `Read` view with a corner edit\n * button that opens the `Edit`/auto-form in an app-provided panel (popover).\n * Best for config-driven blocks whose render differs from their props\n * (custom HTML, charts, any user-registered block).\n * - `\"container\"` — the block renders its `Edit` in place, and that editor\n * may call `ctx.renderBlocksEditor` for nested block regions with normal\n * slash commands and nested structured blocks.\n * Defaults to `\"inline\"` when a custom `Edit` is supplied, else `\"panel\"`\n * (auto-form blocks are property forms, ideal for a panel). The app must wire\n * `ctx.renderEditSurface` for `\"panel\"` to take effect; otherwise it falls\n * back to inline.\n */\n editSurface?: \"inline\" | \"panel\" | \"container\";\n /**\n * Optional generic contract for content-bearing container blocks. Keep this\n * runtime-oriented: it describes editable regions over normalized block arrays;\n * source formats can provide readable nested MDX adapters independently.\n */\n container?: BlockContainerSpec<TData>;\n /** Human label for menus + agent schema export. */\n label: string;\n /** Tabler icon component for UI menus (never emoji/robot/sparkle). */\n icon?: FC<{ size?: number; className?: string }>;\n /** One-line description for the agent schema export. */\n description: string;\n /** Optional default `data` factory for slash-menu insertion (an empty block). */\n empty?: () => TData;\n /**\n * Optional block-specific source-patch handlers, generalizing bespoke ops\n * like `update-custom-html`. Keyed by op name; the registry dispatches a\n * matching patch op here. Generic ops (`update-block` shallow-merge) need none.\n */\n patches?: Record<string, (data: TData, op: Record<string, unknown>) => TData>;\n}\n\n/** Identity helper for authoring a spec with full type inference. */\nexport function defineBlock<TData>(spec: BlockSpec<TData>): BlockSpec<TData> {\n return spec;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/client/blocks/types.ts"],"names":[],"mappings":"AA2WA,qEAAqE;AACrE,MAAM,UAAU,WAAW,CAAQ,IAAsB;IACvD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import type { FC } from \"react\";\nimport type { ZodType } from \"zod\";\n\n/**\n * Block-registry contract. A `BlockSpec` describes one document block end to end:\n * its data shape (`schema`), how it round-trips to MDX source (`mdx`), how it\n * renders read-only (`Read`) and how it is edited (`Edit`, or an auto-generated\n * schema-driven editor when omitted), where it can be placed (`placement`), and\n * metadata for menus / agent schema export.\n *\n * The registry runs ALONGSIDE existing per-block code (the plan `PlanBlockView`\n * switch + `serializeBlock`/`parseBlock`). Renderers check the registry first;\n * unregistered block types fall through to the legacy code path unchanged. The\n * MDX `tag` and attribute shape for a converted block MUST match the historical\n * encoding (e.g. `<Callout tone>…body…</Callout>`) so stored `.mdx` files still\n * parse byte-compatibly.\n */\n\n/** Where a block can be placed in a document. */\nexport type BlockPlacement = \"block\" | \"inline\";\n\n/**\n * A serialized MDX/NFM attribute value before the shared `prop()` encoder runs.\n * `prop()` decides string-vs-JSON encoding; this is just the value domain.\n */\nexport type MdxAttrValue =\n | string\n | number\n | boolean\n | unknown[]\n | Record<string, unknown>;\n\n/**\n * Type-narrowed reader over the resolved MDX attributes of a parsed block node.\n * The values are already estree/JSON-resolved by the shared attribute reader\n * (the same engine `plan-mdx.ts` uses), so a spec's `fromAttrs` never touches\n * the AST directly.\n */\nexport interface BlockAttrReader {\n string(name: string): string | undefined;\n number(name: string): number | undefined;\n bool(name: string): boolean | undefined;\n array<T = unknown>(name: string): T[] | undefined;\n object<T = unknown>(name: string): T | undefined;\n raw(name: string): unknown;\n}\n\n/**\n * Maps a block's validated data to/from its MDX component representation.\n * `tag` is the JSX component name in source (e.g. \"Callout\"). It MUST match the\n * historical name in `plan-mdx.ts` `BLOCK_COMPONENTS` / stored `.mdx` files or\n * existing plans break.\n */\nexport interface BlockMdxConfig<TData> {\n /** JSX component name in MDX source. Stable contract — never rename. */\n tag: string;\n /**\n * Encode `data` → a flat attribute bag. The registry serializer runs each\n * value through the shared `prop()` encoder (string-vs-JSON heuristic) and\n * preserves insertion order, so write the keys in the exact historical order.\n * Return `undefined` for a key (or omit it) to drop the attribute. When\n * `childrenField` is set, that field is excluded from the attribute bag.\n */\n toAttrs: (data: TData) => Record<string, MdxAttrValue | undefined>;\n /**\n * Decode resolved attributes (+ optional children markdown) → data. Must\n * tolerate missing/partial attributes for backward-compat (mirror today's\n * `?? []` / `?? \"\"` defaults).\n */\n fromAttrs: (attrs: BlockAttrReader, children: string) => TData;\n /**\n * When set, this data field is a markdown string serialized as MDX *children*\n * between the open/close tags (prose-bearing blocks: rich-text, callout)\n * rather than as a prop — so the body survives as real, inline-editable MDX\n * prose in source.\n */\n childrenField?: keyof TData & string;\n /**\n * Opt-in custom children serializer for blocks whose internals are nested MDX\n * components rather than a single markdown string (e.g. wireframe → Screen/kit\n * primitives). When present it overrides `childrenField`. `serializeChildren`\n * returns the raw inner MDX; `parseChildren` receives the child MDX AST nodes.\n */\n serializeChildren?: (data: TData) => string;\n parseChildren?: (childNodes: unknown[], idContext: string) => Partial<TData>;\n}\n\n/**\n * App-injected capabilities. Core blocks stay app-agnostic by taking these\n * rather than importing app services — mirroring `createImageExtension`'s\n * `onImageUpload` injection. Provided via `BlockRegistryProvider`.\n */\nexport interface BlockRenderContext {\n /** Markdown dialect for the auto-editor's rich-text field. */\n dialect?: \"gfm\" | \"nfm\";\n /** Resolve an asset id → displayable URL. */\n resolveAssetSrc?: (assetId: string) => string | undefined;\n /** Open the shared asset picker (returns the chosen asset). */\n pickAsset?: () => Promise<{ assetId: string; url?: string } | null>;\n /** Upload a local file, returns a hosted URL. */\n uploadFile?: (file: File) => Promise<{ url: string; assetId?: string }>;\n /** Call an app action by name (for blocks that fetch live data). */\n callAction?: (name: string, args: unknown) => Promise<unknown>;\n /** Sanitizer for HTML-bearing blocks. Provided by the app/core. */\n sanitizeHtml?: (html: string, css?: string) => string;\n /**\n * Render a markdown string with the app's read-only markdown renderer. Lets a\n * core block (whose `Read` lives in core) defer prose rendering to the app's\n * markdown reader (e.g. the plan `PlanMarkdownReader`) without importing it.\n */\n renderMarkdown?: (\n markdown: string,\n options?: { className?: string },\n ) => React.ReactNode;\n /**\n * Render an inline, editable rich-markdown field. The auto-editor calls this\n * for a `markdown()`-tagged field so the app owns the editor wiring (collab,\n * autosave debounce, dialect) rather than core hardcoding it.\n */\n renderMarkdownEditor?: (props: {\n value: string;\n onChange: (next: string) => void;\n editable: boolean;\n blockId?: string;\n className?: string;\n ariaLabel?: string;\n }) => React.ReactNode;\n /**\n * Render an app-owned edit-by-prompt affordance (\"Describe a change…\") for a focused/editable block\n * field. Core block editors pass the current field value and nearby companion\n * fields; the host app decides how to collect the prompt and route it to the\n * agent sidebar. This keeps reusable core blocks from importing app-specific\n * popover/composer code while still exposing a generic AI edit hook.\n */\n renderAiFieldAction?: (props: BlockAiFieldActionProps) => React.ReactNode;\n /**\n * Render a nested child block through the app's own block dispatcher. Container\n * blocks whose `Read`/`Edit` live in core (e.g. tabs) call this to render each\n * child so the recursion keeps flowing through the SAME app renderer the\n * top-level document uses — registered children render via their spec, and\n * unregistered (not-yet-converted) children still fall through the app's legacy\n * switch. This is the coexistence seam: a core container never has to know\n * about app-specific child block types. Returns `null`/`undefined` when no\n * dispatcher is wired (read-only/SSR-only contexts can omit it).\n */\n renderBlock?: (props: {\n block: NestedBlock;\n /** Commit a replacement for this child block (edit mode only). */\n onChange?: (next: NestedBlock) => void;\n /** Whether the parent container is being edited. */\n editing?: boolean;\n /** Tighten embedded visuals in dense contexts (e.g. tab panes). */\n compactVisuals?: boolean;\n }) => React.ReactNode;\n /**\n * Render a nested editable block list through the host app's document editor.\n * Container blocks such as columns call this for each editable region so slash\n * commands, nested structured blocks, and ordinary prose behave like the\n * top-level document while the container still persists its normalized runtime\n * data. Source adapters may still expose a human-friendly nested MDX form\n * (for example `<Columns><Column>markdown</Column></Columns>`) and normalize it\n * into these block arrays at runtime.\n */\n renderBlocksEditor?: (props: {\n blocks: NestedBlock[];\n onChange: (blocks: NestedBlock[]) => void;\n editable: boolean;\n containerBlockId: string;\n regionId: string;\n regionLabel?: string;\n /** Tighten embedded visuals in dense regions such as tab panes. */\n compactVisuals?: boolean;\n }) => React.ReactNode;\n /**\n * Wrap a block's edit form in an app-provided \"panel\" surface (e.g. a shadcn\n * Popover anchored to the corner edit button) for `editSurface: \"panel\"`\n * blocks. Core renders the rendered `Read` view plus a corner trigger button\n * and the form, then hands them here so the app owns the overlay primitive\n * (core stays shadcn-free, mirroring `renderMarkdownEditor`). When omitted, a\n * panel-mode block falls back to inline editing. `title` is the block label.\n */\n renderEditSurface?: (props: {\n title: string;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n trigger: React.ReactNode;\n children: React.ReactNode;\n /** Metadata for host-provided contextual controls such as the edit-by-prompt CTA. */\n blockId?: string;\n blockType?: string;\n blockTitle?: string;\n blockSummary?: string;\n blockData?: unknown;\n }) => React.ReactNode;\n /**\n * Submit a respondent's answers from a `question-form` / `visual-questions`\n * block back to the host. The app decides how to route the summary (e.g. send\n * to the inline agent, copy to clipboard). Core blocks call this through the\n * context so they never import app-specific submit wiring; omit it and the\n * block degrades to a no-op submit.\n */\n onQuestionFormSubmit?: (summary: string) => void;\n}\n\nexport interface BlockAiFieldActionProps {\n blockId: string;\n blockType: string;\n blockTitle?: string;\n blockSummary?: string;\n fieldLabel: string;\n fieldValue: string;\n draftScope: string;\n disabled?: boolean;\n /**\n * Human-readable instructions for the host agent prompt. Mention how to patch\n * the block and which sibling fields should be preserved.\n */\n instructions: string;\n companionFields?: Array<{\n label: string;\n value: string;\n language?: string;\n }>;\n}\n\n/**\n * The minimal shape of a nested child block passed to {@link\n * BlockRenderContext.renderBlock}. It mirrors the app's block union loosely (the\n * app casts it back to its own block type) — a discriminating `type`, a stable\n * `id`, optional heading/summary, and the type-specific `data`.\n */\nexport interface NestedBlock {\n type: string;\n id: string;\n title?: string;\n summary?: string;\n data: unknown;\n [key: string]: unknown;\n}\n\nexport interface BlockContainerRegion {\n id: string;\n label?: string;\n blocks: NestedBlock[];\n}\n\nexport interface BlockContainerSpec<TData> {\n regions: (data: TData) => BlockContainerRegion[];\n updateRegion: (data: TData, regionId: string, blocks: NestedBlock[]) => TData;\n addRegion?: (data: TData, afterRegionId?: string) => TData;\n removeRegion?: (data: TData, regionId: string) => TData;\n reorderRegion?: (\n data: TData,\n fromRegionId: string,\n toRegionId: string,\n ) => TData;\n}\n\nexport type BlockDataChangeMeta = {\n containerRegion?: {\n regionId: string;\n blocks: NestedBlock[];\n };\n};\n\n/** Props passed to a block's read-only renderer. */\nexport interface BlockReadProps<TData> {\n data: TData;\n /** Stable block id (for anchors, comment targeting, source patches). */\n blockId: string;\n /** Block heading, when present. */\n title?: string;\n /** Block trailing summary, when present. */\n summary?: string;\n /** Injected app capabilities. */\n ctx: BlockRenderContext;\n}\n\n/** Props passed to a block's editor (custom or schema-generated). */\nexport interface BlockEditProps<TData> {\n data: TData;\n onChange: (next: TData, meta?: BlockDataChangeMeta) => void;\n editable: boolean;\n blockId: string;\n title?: string;\n summary?: string;\n /** Injected app capabilities. */\n ctx: BlockRenderContext;\n}\n\nexport interface BlockSpec<TData = unknown> {\n /** Discriminator. Equals the runtime block `type`. */\n type: string;\n /** Zod schema for `data`. Drives validation AND the schema-auto-editor. */\n schema: ZodType<TData>;\n /** MDX round-trip config. */\n mdx: BlockMdxConfig<TData>;\n /** Read-only renderer (replaces a `PlanBlockView` switch branch / NodeView). */\n Read: FC<BlockReadProps<TData>>;\n /**\n * Optional editor. When omitted, the registry renders the schema-driven\n * `SchemaBlockEditor` generated from `schema`. Supply for full control\n * (wireframe canvas, diagram editor).\n */\n Edit?: FC<BlockEditProps<TData>>;\n /** Allowed placements: `[\"block\"]`, `[\"inline\"]`, or both. */\n placement: BlockPlacement[];\n /**\n * When `true`, this block's data maps to a Notion-Flavored-Markdown (NFM)\n * analog and therefore round-trips into a Notion page. Apps can derive\n * registry-backed Notion allowlists with\n * {@link BlockRegistry.notionCompatibleTypes} instead of hand-maintaining\n * per-app sets. Set it on registry-atom blocks with an NFM counterpart\n * (checklist, table); leave it `false`/undefined on dev-doc blocks\n * (api-endpoint, openapi-spec, data-model, diff, file-tree, json-explorer,\n * annotated-code, mermaid, custom-html, tabs, code-tabs) and visual/plan-only\n * blocks (wireframe, diagram). Prose blocks that aren't registry atoms\n * (rich-text, callout) carry their NFM analog through the prose path, not this\n * flag.\n */\n notionCompatible?: boolean;\n /**\n * How the block is edited in a `block`-placed document:\n * - `\"inline\"` — the `Edit`/auto-form renders in place for direct\n * manipulation of authored content (prose, checklist text, table cells,\n * code bodies). Schema-ish metadata such as tone/type, tab labels,\n * language, density, or structural settings should still be tucked behind a\n * contextual edit/settings affordance inside the custom `Edit`.\n * - `\"panel\"` — the block shows its rendered `Read` view with a corner edit\n * button that opens the `Edit`/auto-form in an app-provided panel (popover).\n * Best for config-driven blocks whose render differs from their props\n * (custom HTML, charts, any user-registered block).\n * - `\"container\"` — the block renders its `Edit` in place, and that editor\n * may call `ctx.renderBlocksEditor` for nested block regions with normal\n * slash commands and nested structured blocks.\n * Defaults to `\"inline\"` when a custom `Edit` is supplied, else `\"panel\"`\n * (auto-form blocks are property forms, ideal for a panel). The app must wire\n * `ctx.renderEditSurface` for `\"panel\"` to take effect; otherwise it falls\n * back to inline.\n */\n editSurface?: \"inline\" | \"panel\" | \"container\";\n /**\n * Optional generic contract for content-bearing container blocks. Keep this\n * runtime-oriented: it describes editable regions over normalized block arrays;\n * source formats can provide readable nested MDX adapters independently.\n */\n container?: BlockContainerSpec<TData>;\n /** Human label for menus + agent schema export. */\n label: string;\n /** Tabler icon component for UI menus (never emoji/robot/sparkle). */\n icon?: FC<{ size?: number; className?: string }>;\n /** One-line description for the agent schema export. */\n description: string;\n /** Optional default `data` factory for slash-menu insertion (an empty block). */\n empty?: () => TData;\n /**\n * Optional block-specific source-patch handlers, generalizing bespoke ops\n * like `update-custom-html`. Keyed by op name; the registry dispatches a\n * matching patch op here. Generic ops (`update-block` shallow-merge) need none.\n */\n patches?: Record<string, (data: TData, op: Record<string, unknown>) => TData>;\n}\n\n/** Identity helper for authoring a spec with full type inference. */\nexport function defineBlock<TData>(spec: BlockSpec<TData>): BlockSpec<TData> {\n return spec;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DragHandle.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/DragHandle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAQzC,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,oCAAoC,2BAA2B,CAAC;AAE7E,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC9B,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,eAAe,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC;IACd;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,CACxB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;QACP,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,eAAe,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,UAAU,CAAC;KACxB,KACE,IAAI,CAAC;IACV;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC;CACzE;
|
|
1
|
+
{"version":3,"file":"DragHandle.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/DragHandle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAQzC,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,oCAAoC,2BAA2B,CAAC;AAE7E,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC9B,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,eAAe,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC;KACb,KAAK,OAAO,CAAC;IACd;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,CACxB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;QACP,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,eAAe,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,UAAU,CAAC;KACxB,KACE,IAAI,CAAC;IACV;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,KAAK,OAAO,CAAC;CACzE;AAqCD,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5E,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,uBAAuB,CAAC;CACpC,CAAC;AAiWF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,UAAU,mCA67BrB,CAAC"}
|
|
@@ -12,9 +12,23 @@ import { Plugin, PluginKey, NodeSelection, TextSelection, } from "@tiptap/pm/sta
|
|
|
12
12
|
export const DEFAULT_DRAG_HANDLE_WRAPPER_SELECTOR = ".visual-editor-wrapper";
|
|
13
13
|
const dragHandleKey = new PluginKey("dragHandle");
|
|
14
14
|
const HOVER_SIDE_OUTSET_REM = 8;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// Notion-style side drop: drag a block to a neighbour's LEFT/RIGHT region and it
|
|
16
|
+
// builds (or joins) a column layout instead of reordering. The activation region
|
|
17
|
+
// has to be GENEROUS or the gesture is dead for a real human — a natural drag
|
|
18
|
+
// releases somewhere over the block's body, nowhere near a thin edge sliver. The
|
|
19
|
+
// old values (28% of width, capped at 140px, AND only the vertical middle 60%)
|
|
20
|
+
// left a wide ~820px plan block with two ~17%-of-width edge slivers in a 35px-tall
|
|
21
|
+
// band as the ONLY column targets — ~66% of the block (the whole centre) plus the
|
|
22
|
+
// top/bottom only ever reordered, so "drag side by side" essentially never made
|
|
23
|
+
// columns. Now each side claims ~a third of the width across the FULL block
|
|
24
|
+
// height, with a middle band always preserved for before/after reorder.
|
|
25
|
+
const SIDE_DROP_ZONE_RATIO = 0.33;
|
|
26
|
+
const SIDE_DROP_ZONE_MIN_PX = 56;
|
|
27
|
+
const SIDE_DROP_ZONE_MAX_PX = 320;
|
|
28
|
+
// Never let the two side zones swallow the whole block: keep at least the middle
|
|
29
|
+
// ~10% of the width as the before/after reorder band so dropping over the centre
|
|
30
|
+
// still moves the block above/below the target (Notion keeps reorder reachable).
|
|
31
|
+
const SIDE_DROP_ZONE_MAX_WIDTH_FRACTION = 0.45;
|
|
18
32
|
const DRAG_HANDLE_MENU_STYLE_ID = "an-rich-md-drag-menu-styles";
|
|
19
33
|
const DRAG_HANDLE_MENU_WIDTH = 220;
|
|
20
34
|
const DRAG_HANDLE_MENU_GAP = 6;
|
|
@@ -22,6 +36,12 @@ const DRAG_HANDLE_MENU_VIEWPORT_PADDING = 8;
|
|
|
22
36
|
const dragHandleRegistrations = new Set();
|
|
23
37
|
let dragHandleGlobalHoverListeners = 0;
|
|
24
38
|
let activeDragRegistration = null;
|
|
39
|
+
// The registration whose grip is currently shown. Used to keep that grip alive
|
|
40
|
+
// while the cursor travels from a block's body to its grip, even when the grip
|
|
41
|
+
// sits in a contested gap (an inter-column gap or a tab body's left offset)
|
|
42
|
+
// where another editor's wide forgiving zone would otherwise re-win the hover
|
|
43
|
+
// and hide the grip out from under the approaching cursor.
|
|
44
|
+
let activeHoverRegistration = null;
|
|
25
45
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
26
46
|
const editorArea = (registration) => {
|
|
27
47
|
const rect = registration.view.dom.getBoundingClientRect();
|
|
@@ -32,6 +52,7 @@ const updateRegisteredHover = (clientX, clientY) => {
|
|
|
32
52
|
for (const registration of dragHandleRegistrations) {
|
|
33
53
|
registration.hideHover?.();
|
|
34
54
|
}
|
|
55
|
+
activeHoverRegistration = null;
|
|
35
56
|
return;
|
|
36
57
|
}
|
|
37
58
|
const candidates = [];
|
|
@@ -48,13 +69,88 @@ const updateRegisteredHover = (clientX, clientY) => {
|
|
|
48
69
|
registration.hideHover?.();
|
|
49
70
|
}
|
|
50
71
|
}
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
// Grip keepalive. Once a block's grip is showing, hold it while the cursor
|
|
73
|
+
// travels LEFT of that block's content toward its grip glyph — within the
|
|
74
|
+
// block's own vertical row and no further left than the glyph itself. This is
|
|
75
|
+
// what makes grips grabbable for blocks that are NOT flush with the page's
|
|
76
|
+
// left gutter (a right column, a tab body): their grip sits in a gap that the
|
|
77
|
+
// neighbour's wide forgiving zone also claims, so the normal picker would flip
|
|
78
|
+
// hover to the neighbour mid-approach and the grip would vanish before the
|
|
79
|
+
// cursor reaches it. The keepalive only bridges the body→grip gap — it does
|
|
80
|
+
// NOT fire while the cursor is over content (so the innermost/nested picking
|
|
81
|
+
// and gutter-grab rules below still decide there) and the row guard stops it
|
|
82
|
+
// from sticking the grip across vertical moves to another block's row.
|
|
83
|
+
if (activeHoverRegistration) {
|
|
84
|
+
const held = candidates.find((candidate) => candidate.registration === activeHoverRegistration);
|
|
85
|
+
const grip = activeHoverRegistration.gripRect?.();
|
|
86
|
+
if (held &&
|
|
87
|
+
grip &&
|
|
88
|
+
clientY >= held.block.rect.top &&
|
|
89
|
+
clientY < held.block.rect.bottom &&
|
|
90
|
+
clientX >= grip.left - 4 &&
|
|
91
|
+
clientX < held.block.rect.left) {
|
|
92
|
+
for (const registration of dragHandleRegistrations) {
|
|
93
|
+
if (registration !== held.registration)
|
|
94
|
+
registration.hideHover?.();
|
|
95
|
+
}
|
|
96
|
+
held.registration.showHoverBlock?.(held.block);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Pick which editor owns the grip when several register a hover block at this
|
|
101
|
+
// point. Nested region editors (e.g. each column inside a `columns` block) tile
|
|
102
|
+
// their container's whole footprint AND extend a wide forgiving zone
|
|
103
|
+
// (HOVER_SIDE_OUTSET_REM) into its left-margin gutter, so a pure
|
|
104
|
+
// "smallest editor wins" rule lets an inner block beat the container everywhere
|
|
105
|
+
// and leaves the container itself impossible to grab. Split candidates by where
|
|
106
|
+
// the cursor sits relative to each block:
|
|
107
|
+
// - Over a block's body (clientX at/after its left edge) the innermost
|
|
108
|
+
// (smallest) editor wins, so nested blocks stay grabbable from their content.
|
|
109
|
+
// - In the shared left-margin gutter (clientX left of every candidate's
|
|
110
|
+
// content, where the grip lives) the outermost (largest) editor wins, so the
|
|
111
|
+
// container block can be picked up and reordered.
|
|
112
|
+
// Prefer the candidate whose block actually sits UNDER the cursor
|
|
113
|
+
// horizontally. Without this, a left column's forgiving side zone reaches
|
|
114
|
+
// across the inter-column gap, ties the right column's editor on area, and
|
|
115
|
+
// wins — so hovering a right-column block shows the grip for the LEFT block
|
|
116
|
+
// (and right-column blocks appear to have no grip at all). `overContent`
|
|
117
|
+
// restricts to blocks the cursor is genuinely within; `rightOfLeftEdge` keeps
|
|
118
|
+
// the gutter-grab behaviour; fully left of every block → the container wins.
|
|
119
|
+
const overContent = candidates.filter((candidate) => clientX >= candidate.block.rect.left &&
|
|
120
|
+
clientX <= candidate.block.rect.right);
|
|
121
|
+
// The grip renders in a narrow band just LEFT of each block (≈24px). A block
|
|
122
|
+
// must OWN that band so moving the cursor onto its grip keeps showing (and
|
|
123
|
+
// lets you press) that block's grip — otherwise, for a column block whose grip
|
|
124
|
+
// sits in the gutter/inter-column gap, the "gutter → largest editor" rule
|
|
125
|
+
// below would flip the hover to the columns container and the grip would
|
|
126
|
+
// vanish out from under the cursor, making inner column blocks impossible to
|
|
127
|
+
// drag. The band is narrow, so it does not collide with the neighbouring
|
|
128
|
+
// column's content (the right column's grip lives in the inter-column gap,
|
|
129
|
+
// left of its own content but right of the left column's content).
|
|
130
|
+
const GRIP_HOVER_ZONE_PX = 28;
|
|
131
|
+
const overGrip = candidates.filter((candidate) => clientX >= candidate.block.rect.left - GRIP_HOVER_ZONE_PX &&
|
|
132
|
+
clientX < candidate.block.rect.left);
|
|
133
|
+
const rightOfLeftEdge = candidates.filter((candidate) => clientX >= candidate.block.rect.left);
|
|
134
|
+
let active;
|
|
135
|
+
const innerPool = overContent.length > 0
|
|
136
|
+
? overContent
|
|
137
|
+
: overGrip.length > 0
|
|
138
|
+
? overGrip
|
|
139
|
+
: rightOfLeftEdge;
|
|
140
|
+
if (innerPool.length > 0) {
|
|
141
|
+
innerPool.sort((a, b) => editorArea(a.registration) - editorArea(b.registration));
|
|
142
|
+
active = innerPool[0];
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
candidates.sort((a, b) => editorArea(b.registration) - editorArea(a.registration));
|
|
146
|
+
active = candidates[0] ?? null;
|
|
147
|
+
}
|
|
53
148
|
for (const registration of dragHandleRegistrations) {
|
|
54
149
|
if (registration !== active?.registration)
|
|
55
150
|
registration.hideHover?.();
|
|
56
151
|
}
|
|
57
152
|
active?.registration.showHoverBlock?.(active.block);
|
|
153
|
+
activeHoverRegistration = active?.registration ?? null;
|
|
58
154
|
};
|
|
59
155
|
const handleGlobalHoverMove = (event) => {
|
|
60
156
|
updateRegisteredHover(event.clientX, event.clientY);
|
|
@@ -567,18 +663,17 @@ export const DragHandle = Extension.create({
|
|
|
567
663
|
return null;
|
|
568
664
|
let placement;
|
|
569
665
|
const withinBlockY = clientY >= block.rect.top && clientY <= block.rect.bottom;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
666
|
+
// Side (column) zones span the FULL block height — only the horizontal
|
|
667
|
+
// position decides column-vs-reorder. Restricting to the vertical middle
|
|
668
|
+
// (the old 0.2 band) made the already-tiny edge slivers nearly unhittable.
|
|
669
|
+
const sideZoneWidth = Math.min(clamp(block.rect.width * SIDE_DROP_ZONE_RATIO, SIDE_DROP_ZONE_MIN_PX, SIDE_DROP_ZONE_MAX_PX), block.rect.width * SIDE_DROP_ZONE_MAX_WIDTH_FRACTION);
|
|
573
670
|
if (registration.handleDrop &&
|
|
574
671
|
withinBlockY &&
|
|
575
|
-
withinSideDropBand &&
|
|
576
672
|
clientX <= block.rect.left + sideZoneWidth) {
|
|
577
673
|
placement = "left";
|
|
578
674
|
}
|
|
579
675
|
else if (registration.handleDrop &&
|
|
580
676
|
withinBlockY &&
|
|
581
|
-
withinSideDropBand &&
|
|
582
677
|
clientX >= block.rect.right - sideZoneWidth) {
|
|
583
678
|
placement = "right";
|
|
584
679
|
}
|
|
@@ -629,11 +724,20 @@ export const DragHandle = Extension.create({
|
|
|
629
724
|
};
|
|
630
725
|
const updateDropLine = (session, target) => {
|
|
631
726
|
const sourceEnd = session.sourcePos + session.sourceNodeSize;
|
|
727
|
+
const isSideDrop = target?.placement === "left" || target?.placement === "right";
|
|
632
728
|
if (!target ||
|
|
633
729
|
(target.view === session.view &&
|
|
634
|
-
(
|
|
635
|
-
|
|
636
|
-
|
|
730
|
+
(isSideDrop
|
|
731
|
+
? // A side drop only ever builds columns; the ProseMirror seam
|
|
732
|
+
// position is irrelevant. The only no-op is dropping a block on
|
|
733
|
+
// ITS OWN side — adjacent *different* blocks must still form
|
|
734
|
+
// columns (otherwise dropping onto an immediate neighbour's
|
|
735
|
+
// facing edge silently does nothing, which reads as "side drop
|
|
736
|
+
// works sometimes").
|
|
737
|
+
target.targetPos === session.sourcePos
|
|
738
|
+
: target.pos === session.sourcePos ||
|
|
739
|
+
target.pos === sourceEnd ||
|
|
740
|
+
(target.pos > session.sourcePos && target.pos < sourceEnd)))) {
|
|
637
741
|
session.dropTarget = null;
|
|
638
742
|
session.dropLine?.remove();
|
|
639
743
|
session.dropLine = null;
|
|
@@ -651,11 +755,21 @@ export const DragHandle = Extension.create({
|
|
|
651
755
|
const wrapperRect = wrapper.getBoundingClientRect();
|
|
652
756
|
const editorRect = target.view.dom.getBoundingClientRect();
|
|
653
757
|
session.dropTarget = target;
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
758
|
+
// A column (side) drop and a reorder (before/after) drop both draw the
|
|
759
|
+
// `.notion-drop-indicator`, but they mean very different things, so the
|
|
760
|
+
// column case carries a modifier class apps style distinctly (a bolder,
|
|
761
|
+
// glowing vertical bar) — without a clear cue a human can't tell they've
|
|
762
|
+
// entered column-build mode before releasing.
|
|
763
|
+
const isColumnDrop = target.placement === "left" || target.placement === "right";
|
|
764
|
+
session.dropLine.classList.toggle("notion-drop-indicator--column", isColumnDrop);
|
|
765
|
+
if (isColumnDrop) {
|
|
766
|
+
// A vertical bar centred on the seam at the target's left/right edge,
|
|
767
|
+
// spanning the block's full height.
|
|
768
|
+
const SIDE_BAR_WIDTH = 4;
|
|
769
|
+
const seam = target.placement === "left" ? target.rect.left : target.rect.right;
|
|
770
|
+
session.dropLine.style.left = `${seam - wrapperRect.left - SIDE_BAR_WIDTH / 2}px`;
|
|
657
771
|
session.dropLine.style.top = `${target.rect.top - wrapperRect.top}px`;
|
|
658
|
-
session.dropLine.style.width =
|
|
772
|
+
session.dropLine.style.width = `${SIDE_BAR_WIDTH}px`;
|
|
659
773
|
session.dropLine.style.height = `${target.rect.height}px`;
|
|
660
774
|
return;
|
|
661
775
|
}
|
|
@@ -676,7 +790,13 @@ export const DragHandle = Extension.create({
|
|
|
676
790
|
el.setAttribute("aria-haspopup", "menu");
|
|
677
791
|
el.setAttribute("aria-expanded", "false");
|
|
678
792
|
el.title = "Open block menu or drag to reorder";
|
|
679
|
-
|
|
793
|
+
// The icon must not be its own hit target: a real mouse-down inside a
|
|
794
|
+
// nested editor (a column) lands on the SVG, and a container block's
|
|
795
|
+
// capture-phase block-select handler (RegistryBlockNode) only spares the
|
|
796
|
+
// grip DIV — so a press on the icon gets swallowed and the block can't be
|
|
797
|
+
// dragged out of / between columns. `pointer-events:none` makes every
|
|
798
|
+
// press in the grip area resolve to the DIV instead.
|
|
799
|
+
el.innerHTML = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" style="pointer-events:none">
|
|
680
800
|
<circle cx="5.5" cy="3" r="1.5"/><circle cx="10.5" cy="3" r="1.5"/>
|
|
681
801
|
<circle cx="5.5" cy="8" r="1.5"/><circle cx="10.5" cy="8" r="1.5"/>
|
|
682
802
|
<circle cx="5.5" cy="13" r="1.5"/><circle cx="10.5" cy="13" r="1.5"/>
|
|
@@ -748,10 +868,15 @@ export const DragHandle = Extension.create({
|
|
|
748
868
|
const sourceEnd = session.sourcePos + session.sourceNodeSize;
|
|
749
869
|
const target = session.dropTarget;
|
|
750
870
|
const dropPos = target.pos;
|
|
871
|
+
const isSideDrop = target.placement === "left" || target.placement === "right";
|
|
751
872
|
if (target.view !== session.view ||
|
|
752
|
-
(
|
|
753
|
-
|
|
754
|
-
|
|
873
|
+
(isSideDrop
|
|
874
|
+
? // Side drop (column build): proceed for any block that isn't the
|
|
875
|
+
// source itself, including the source's immediate neighbour.
|
|
876
|
+
target.targetPos !== sourceStart
|
|
877
|
+
: dropPos !== sourceStart &&
|
|
878
|
+
dropPos !== sourceEnd &&
|
|
879
|
+
!(dropPos > sourceStart && dropPos < sourceEnd))) {
|
|
755
880
|
const sourceNode = session.view.state.doc.nodeAt(sourceStart);
|
|
756
881
|
if (sourceNode) {
|
|
757
882
|
const sourceRegistration = registrationForView(session.view);
|
|
@@ -873,6 +998,9 @@ export const DragHandle = Extension.create({
|
|
|
873
998
|
findHoverBlock: (clientX, clientY) => findForgivingBlock(editorView, clientX, clientY),
|
|
874
999
|
showHoverBlock: (block) => showHandleForBlock(editorView, block),
|
|
875
1000
|
hideHover: () => hideHandle(),
|
|
1001
|
+
gripRect: () => handle && handle.style.display !== "none"
|
|
1002
|
+
? handle.getBoundingClientRect()
|
|
1003
|
+
: null,
|
|
876
1004
|
};
|
|
877
1005
|
currentRegistration = registration;
|
|
878
1006
|
dragHandleRegistrations.add(registration);
|
|
@@ -944,6 +1072,9 @@ export const DragHandle = Extension.create({
|
|
|
944
1072
|
if (activeDragRegistration === registration) {
|
|
945
1073
|
activeDragRegistration = null;
|
|
946
1074
|
}
|
|
1075
|
+
if (activeHoverRegistration === registration) {
|
|
1076
|
+
activeHoverRegistration = null;
|
|
1077
|
+
}
|
|
947
1078
|
handle?.remove();
|
|
948
1079
|
handle = null;
|
|
949
1080
|
currentRegistration = null;
|