feat: expose public sdt events#3509
Conversation
|
📖 Docs preview: https://superdoc-artem-sd-3232.mintlify.app |
8dcd42c to
1748f61
Compare
1748f61 to
5206f9f
Compare
5206f9f to
d485941
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d48594154a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
cubic analysis
1 issue found across 8 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/super-editor/src/editors/v1/core/Editor.ts">
<violation number="1" location="packages/super-editor/src/editors/v1/core/Editor.ts:386">
P2: `#lastActiveContentControlRef` is not reset in `#unloadDocument()`. When the user opens a different document, the stale reference leaks into the first `contentControlBlur`/`contentControlFocus` event of the new document, sending incorrect `previous` metadata to integrators.</violation>
</file>
Linked issue analysis
Linked issue: SD-3232: Feature: Expose public SDT focus and click interaction events
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | When I click inside an inline SDT, the public SDT interaction API reports the clicked/active SDT with stable metadata. | Editor emits a click event when UI click occurs and resolves an SdtRef for the active SDT; SuperDoc bridges editor click to the public 'content-control:click' event with the SdtRef payload and docs show the payload shape. |
| ✅ | When I click inside a block SDT, the public SDT interaction API reports the clicked/active SDT with stable metadata. | toSdtRef computes scope as 'inline' vs 'block' based on node type and the same click flow emits the SdtRef payload for clicks inside block SDTs; SuperDoc exposes the bridged event and types. |
| ✅ | When I move the cursor/selection from outside an SDT into an SDT, the public SDT interaction API reports the newly active SDT. | Editor detects selection changes, resolves active SDT refs from the new selection, compares to previous, and emits a focus-style event which SuperDoc bridges to 'content-control:active-change'. |
| ✅ | When I move the cursor/selection from one SDT to another SDT, the public SDT interaction API reports the new active SDT. | The implementation compares previous and current SdtRef ids and emits the focus/switch payload (previous + active) when they differ; SuperDoc exposes that payload as 'content-control:active-change'. |
| ✅ | When I move the cursor/selection outside any SDT, the public SDT interaction API reports that no SDT is active or otherwise provides a documented clear/blur signal. | When selection changes to a state with no active SdtRef, the editor emits a blur-style payload (active: null, previous) and SuperDoc bridges this as an active-change event; docs describe null active for blur. |
| ✅ | Event payloads include stable SDT metadata (id, tag, alias, controlType, inline/block scope). | Typed SdtRef is added in editor and SuperDoc types, toSdtRef extracts id/tag/alias/controlType/scope and payloads use SdtRef rather than PM internals; docs show the SdtRef shape. |
| ✅ | The documented public API does not require customer code to depend on ProseMirror-specific data structures. | SuperDoc exposes events and typed payloads (SdtRef) and the bridge converts internal PM nodes to plain metadata; public config callbacks are added to the SuperDoc config types. |
| Preserve existing SDT selection and highlighting behavior while adding the public event surface. | The PR emits events on selection changes and click meta without modifying selection-setting logic, suggesting behavior is preserved, but there are no explicit tests or assertions in the diff that verify that visual selection/highlighting behavior is unchanged. |
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
d485941 to
1e619ad
Compare
1e619ad to
ccacf87
Compare
ccacf87 to
ad8dc29
Compare
ad8dc29 to
8f12821
Compare
8f12821 to
f3333e9
Compare
…, add core tests Address review feedback on the public content-control events: - Editor: clear #lastActiveContentControlRef / #lastPointerDownAt in #unloadDocument so a close/reopen on the same instance does not leak a stale `previous` into the next document's first event (or skip the first focus when ids collide). Document the 800ms pointer-source window as advisory. - types: drop the duplicate SdtRef / payload interfaces in core/SuperDoc (the event map now uses the public ContentControl*Payload from core/types), remove the dead SuperDocEditorUpdatePayload, and export SdtRef + ContentControlActiveChangePayload + ContentControlClickPayload from the public root so consumers can import them by name. - consumer-typecheck: assert the onContentControl* callback params equal the exported payload types. - tests: add real-editor behavior coverage (focus, switch, blur, click, source detection, and a negative programmatic-mutation case) over actual structuredContent nodes, not mocked payloads. - docs: explain events vs ui.contentControls.observe() / getRect(), the config-callback form, and the limitations (events require an SDT id; `active` is the deepest control - use observe() for the full stack).
4bee651 to
d7ee037
Compare
…te export snapshot - Editor: the #lastPointerDownAt listener was only registered in #registerEventListeners() (the deferDocumentLoad path). The default PresentationEditor path uses #init / #initRichText, so pointer-driven selection changes without uiEvent:'click' were reported as source:'keyboard'. Extract a #trackContentControlPointer() helper and call it on all three init paths. - tests: add a regression test (on the default #init path) that a non-click selection after a pointerDown reports source:'pointer', and a block-scope (structuredContentBlock) focus case. - consumer-typecheck: regenerate the root-exports snapshot for the three newly exported payload types so check:public:superdoc passes.
…ctive-change Switch the contextual smart-field chip from ui.contentControls.observe to the public content-control:active-change event (SD-3232) for "which control is active", keeping ui.contentControls.getRect for placement. attachFieldChip now takes the SuperDoc instance plus the UI controller. Built-in chrome stays off (chrome:'none'); the chip comments now describe the events + getRect pairing.
…ol:active-change The event exposed only the deepest active control, while ui.contentControls.observe has the full activeIds stack - so nested-aware custom UI had to combine the two APIs. Add activePath: SdtRef[] (innermost first; active === activePath[0]; empty on blur) to the focus/blur payloads and the public ContentControlActiveChangePayload. The stack was already computed in #collectActiveSdtRefs; drop the now-unused #resolveActiveContentControlRef. Tests cover single inline/block, switch, blur, and a nested inline-in-block case (activePath = [inner, outer]).
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
…trols The activePath paragraph said you can read the nested stack without ui.contentControls.observe(), but the limitations note still told readers to use observe() for the full stack. Reword the limitation: activePath carries the stack on the event; observe() is for ongoing list/value reactivity, not just the active nesting.
…istry events.mdx should list events, not teach a workflow. Trim its content-control section to the active-change/click reference plus a link, and add editor/custom-ui/content-controls with the actual how-to: turn off built-in chrome, react to the active control (and activePath), position with getRect, hit-test via the viewport, and mutate through the document API. Notes current limits (no scroll-to-control, geometry subscription, or focus-by-id helper yet). Supersedes the in-place events.mdx reconcile.
### Bug Fixes - copy-pasted text in suggestion mode (#3576) - center virtualized matches after mount in find nav (SD-3315) - stop find navigation jumping to the reverted caret (SD-3315) - honor the focus() contract + fix dangling docs reference (SD-3312) - don't re-center visible matches on find navigation (SD-3315) - report 'zoom' not 'mixed' for a zoom repaint (SD-3311) - wire pointer-source tracking on all init paths; update export snapshot - reset state on unload, dedupe + export payload types, add core tests - skip empty block SDT content selection - type modules.contentControls exactly (no pass-through index) - allow block SDT wrapper deletion to follow lock rules - promote image-bearing inline SDT wrappers to inline-block - keep block sdt fill behind content - hide block sdt fills in output modes - paint block SDT background on chrome layer - use logical inset for inline SDT label position - anchor inline SDT label to start of chrome - route smartTag through export and preserve smartTagPr in SDT flatten (SD-2647) - include inline SDT chrome width in block SDT bounds - keep block SDT chrome at full fragment width - render and round-trip w:smartTag content (SD-2647) - decouple base64 image helper imports - preserve block ids during metadata updates - ignore covered sdt label clicks - sync block sdt label selection updates - show empty SDT placeholder text in viewing and print modes - skip empty sdt scan on arrow right - preserve permission-only sdt placeholders - preserve comment-only sdt placeholders - preserve empty sdt bookmark placeholders - collapse hidden sdt placeholder text - keep sdt placeholder pm range atomic - trust empty sdt paragraph conversion - keep vanished sdt paragraph side effects - preserve vanished block sdt paragraphs - suppress hidden block sdt chrome - keep remeasured sdt placeholder atomic - transform sdt placeholder measure - remeasure sdt placeholders - hide sdt placeholders in print - hide sdt placeholders in viewing - ignore collapsed inline sdt cut - expose block sdt appearance - hide empty block sdt placeholder - align empty block sdt caret - use measured width for empty SDT placeholders - size SDT block labels to content width - collapse selection on sdtContentLocked delete - allow history transactions through sdt lock - drop unreachable move fallback - target marker-only textblock end - ignore empty block sdt key targets - cap block sdt label width - handle sdt marker gaps and block atoms - skip hidden field annotations in sdt navigation - skip hidden metadata sdt markers - skip hidden block sdt markers - handle marker-only sdt paragraphs - keep visible atoms in sdt navigation - skip hidden sdt navigation markers - target nearest sdt cursor position - respect inline atoms in sdt navigation - handle empty block sdt navigation - avoid restoring dragged block to its source position - exclude sdt chrome labels from caret position lookup - keep text-align enabled in locked SDT paragraphs - block disabled toolbar execution - guard unlisted locked toolbar commands - block locked sdt toolbar execution - reject malformed base64 image data URIs - reuse colliding data uri media targets - enforce upload byte cap for data uris - avoid non-image data uri extensions - reject raw raster data uri dimensions - validate oversized async svg images - register preset raster data uris in place - narrow sdt metadata overrides - normalize image data uri extensions - centralize image data url policy - reuse target image relationships - validate in-place svg payloads - validate field annotation data uri exports - reject malformed data uri files - avoid duplicate image rids - block raw raster data uri exports - reject malformed svg data uri payloads - warn on skipped image exports - reject separatorless data uri files - block non-image data uri exports - read svg data uri dimensions - share data uri media parsing - skip invalid data uri image targets - normalize svg data uri filenames - validate in-place svg image data - guard non-base64 data uri exports - export field annotation svgs as svg - allow non-base64 SVG data URLs in image rendering - mirror in-place image media to parent - decode non-base64 data URI exports - extract shared hash helpers - reuse data URI image exports - support non-base64 data URI images in registration - scope inline SDT placeholder to structuredContent metadata - register sized SVG data URI images without canvas processing - persist data URI images set via setPresetContent - share SDT lock predicates - version inline image metadata - align RTL SDT chrome to text - size SDT chrome for justified lines - honor ancestor image SDT locks - dirty inline image SDT changes - align SDT chrome within paragraph width - preserve SDT chrome continuation offsets - offset block SDT chrome for indents - suppress SDT pseudo hover in viewing mode - allow top-aligned inline images - fit block SDT chrome to actual content width - bottom-align text on lines with inline images - keep block SDT chrome and inline images out of paragraph geometry - disable image resize inside content-locked SDTs - detect inline image run changes in paragraph diff - mark block SDT selected when contained image is selected - select inline SDT content as text on Delete - intercept beforeinput insertText at inline SDT boundaries - delete contentLocked SDT wrapper in one step - bump sdBlockRev on ancestors of inline edits - select inline SDT content as text on Backspace - select inline SDT on Backspace at start of following run (SD-3165) - share structured content chrome label set - resolve block labels at node boundary - use contract label selectors - clear label gesture state on cancel - scope label clicks to owning editor - defer label selection to mouseup so native drag still fires - share structured content label classes - avoid deferred block label retry - select labels with active editor - focus editor after label selection - correct cursor placement and label interactions for structured content - handle cell-level SDT in vMerge column lookup (SD-3289) - preserve cell-level SDT wrapping table cells (SD-3289) - preserve recipient identity attrs on replay (SD-3279) - accept legacy fingerprints in compare/apply (SD-3279) - drop misleading pnpm run type-check hint from audit - structural-fail on missing dist + sync check-jsdoc header - strip session-local sdBlockId from diff fingerprint (SD-3279) - autoFit table width overflow from cell preferences (#3522) - flip public-method-coverage to strict-zero gate (SD-673) - flip jsdoc-hygiene-ts to strict-zero gate (SD-673) - rename jsdoc-hygiene-ts self-tests + wire into CI (fixes vitest discovery on #3511) - jsdoc-hygiene-ts handles private-identifier symbols + README - more bugs - add ui for overlapping delete, other fixes - replacement pair - remaining collab bugs - coalesce tracked inserts across run gaps - restore tracked change comment interactions - expose tracked mark predicate option - more cases - tc fixes - collab mode bug - floating comments fixes - make jsdoc-hygiene-ts baseline key line-independent + update wrapper docs - preserve tab underline via runProperties fallback in collab ### Changes - Merge branch 'main' into caio/sd-3315-find-replace-scroll - Merge pull request #3509 from superdoc-dev/artem/SD-3232 - Merge pull request #3555 from superdoc-dev/luccas/delete-image-content-locked-sdt - Merge branch 'main' into artem/SD-3159 - Merge pull request #3550 from superdoc-dev/luccas/sd-3302-bug-sdt-in-template-builder-shows-grey-background-highlight - Merge branch 'main' into artem/SD-3159 - SD-2676 - fix: table selection not providing a feedback (#3508) - Merge pull request #3549 from superdoc-dev/luccas/left-align-inline-sdt-label - Merge pull request #3546 from superdoc-dev/caio/sd-2647-bug-render-and-round-trip-content-wrapped-in-wsmarttag - Merge remote-tracking branch 'origin/stable' into sync/stable-to-main-20260527-230540 - Merge pull request #3539 from superdoc-dev/caio-pizzol/sd-3289-preserve-cell-level-sdt - Merge pull request #3527 from superdoc-dev/caio-pizzol/sd-3279-strip-sdblockid-from-diff-fingerprint - Merge branch 'main' into caio-pizzol/SD-js-contract-owner-audit - Merge pull request #3531 from superdoc-dev/caio-pizzol/SD-docs-snippet-typecheck-2 - Merge pull request #3526 from superdoc-dev/caio-pizzol/SD-runtime-payload-tests - Merge pull request #3521 from superdoc-dev/caio-pizzol/SD-public-method-coverage-strict-zero - Merge branch 'stable' - Merge pull request #3485 from superdoc-dev/artem/SD-3200 - Merge pull request #3517 from superdoc-dev/caio-pizzol/SD-jsdoc-hygiene-zero-flip - Merge pull request #3513 from superdoc-dev/caio-pizzol/SD-jsdoc-hygiene-cleanup - Merge branch 'stable' - Merge pull request #3511 from superdoc-dev/caio-pizzol/SD-jsdoc-hygiene-scanner - Merge pull request #3435 from superdoc-dev/nick/sd-3220-overlapping-suggestion-contract - Merge pull request #3429 from superdoc-dev/artem/underlined-tab-collab ### Documentation - note content controls in entityAt hit types (SD-3313) - clarify data uri buffer conversion - document image data uri helpers - clarify image registration comments - sync README exit semantics for js contract-owner audit - clarify wrapper-stages prose + add ts-jsdoc to check:public summary ### Features - add ui.contentControls.focus to place the caret in a control (SD-3312) - add ui.viewport.observe geometry-invalidation signal (SD-3311) - add ui.contentControls.scrollIntoView (SD-3310) - add activePath (full active stack) to content-control:active-change - expose public sdt events - select adjacent block SDT content at textblock boundaries - inherit run styles in empty block SDT placeholders - render placeholder text for empty SDTs - move caret into following block sdt on delete - move caret into preceding block sdt on backspace - disable mutation toolbar controls inside content-locked SDTs - render empty inline SDTs as a visible placeholder - add modules.contentControls.chrome - js contract-owner audit, report-only (SD-673) - add snippet typecheck for editor/superdoc/** + fix stale examples (SD-673) - add non-hover field color for sdt (#3506) - comments and tc on small screen (#3446) - overlapping tracked changes - add type-bearing JSDoc hygiene gate for .ts source (SD-673) - add anchored metadata orphan status ### Tests - resolve handleBase64 source path from package or repo root - cover locked block SDT Delete selection - cover nested block SDT boundary selection - update SDT keymap chain coverage - cover contentControlsChrome plumbing; clarify chrome-none comment - assert chrome-none hover suppression and cascade order - add v2 bridge unit + round-trip behavior coverage (SD-2647) - assert smartTag child text survives export round-trip (SD-2647) - cover nested block sdt navigation - roundtrip mixed image block sdts - cover image data uri length boundary - repaint saved sdt images through painter - cover structured content image edges - cover inline image diff fields - cover locked inline SDT beforeinput - cover inline SDT Cmd+X selection - cover inline SDT content Delete flow - clarify inline SDT boundary lock comment - cover two-step inline SDT Backspace - cover inline SDT selection meta escape - use real production payload for list-definitions-change bridge (SD-673) - tighten list-definitions-change bridge assertion (SD-673) - pin list-definitions-change bridge and DELETED comments shape (SD-673) - pin runtime event payload shapes (SD-673) ### Refactoring - consolidate type imports in dom painter - share block sdt navigation helpers - share box model between block and inline sdt labels - share structured content predicates - wrap shared tryDecodeDataUriText re-export - reuse shared data uri export policy - share image relationship export lookup - centralize image data uri parsing - share data uri text decoding - trim data uri metadata fields - extract CHECKED_FILES to shared module + drop audit from wrapper - drop list-item fragment renderer (SD-2851) (#3269) - drain 85 type-bearing JSDoc entries from .ts source (SD-673) ### Performance - avoid scanning data uri media ### Chores - 1.37.0 [skip ci] - fix import breaking delinstrtext orphans (#3535) - fix tests (#3533) - fixes - more fixes - run generate:all and fixes - more fixes - soec fixes - type fixes - ui and more - fix regression - type fixes - more fixes - more fixes - ci fixes - type fixes - add dispatch test for collab bug - review fix, type fix - add tests for metadata issue - tests for review issues
|
🎉 This PR is included in superdoc-cli v0.15.0 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-sdk v1.14.0 |
|
🎉 This PR is included in @superdoc-dev/mcp v0.10.0 The release is available on GitHub release |
|
🎉 This PR is included in superdoc v1.38.0 The release is available on GitHub release |
|
🎉 This PR is included in @superdoc-dev/react v1.9.0 The release is available on GitHub release |
|
🎉 This PR is included in vscode-ext v2.10.0 |
Linear: SD-3232
Add public content control (SDT) interaction events on
SuperDoc.What’s included
contentControlFocuscontentControlBlurcontentControlClickSuperDocevents:content-control:active-changecontent-control:clickSuperDocinSuperDoc.vue.SdtRef, active-change/click payloads) to public types.SuperDocevent map/listeners to include new events and config callbacks:onContentControlActiveChangeonContentControlClickapps/docs/editor/superdoc/events.mdxunder Content controls (SDT fields).Behavior
content-control:active-changecovers enter/switch/leave:null -> A(focus)A -> B(switch)A -> null(blur)content-control:clickfires on pointer click inside SDT.