Skip to content

Commit b1badbc

Browse files
authored
feat: painter clean up (#2632)
* fix(presentation): move editor interaction affordances out of the painter * refactor(presentation): move identity lookups onto the paint snapshot * refactor(presentation): isolate post-paint DOM layers and render coordination * fix(presentation): keep virtualization visibility and annotation labels in sync
1 parent 0920a27 commit b1badbc

28 files changed

Lines changed: 2172 additions & 457 deletions

packages/layout-engine/dom-contract/src/class-names.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ export const DOM_CLASS_NAMES = {
3636
/** Document section container. */
3737
DOCUMENT_SECTION: 'superdoc-document-section',
3838

39-
/** Hover highlight applied to all fragments of the same block SDT. */
40-
SDT_HOVER: 'sdt-hover',
39+
/**
40+
* Grouped hover highlight applied to all fragments of the same block SDT.
41+
* Set by PresentationEditor's hover coordination via event delegation.
42+
*/
43+
SDT_GROUP_HOVER: 'sdt-group-hover',
4144

4245
/** Block-level image fragment (ImageBlock). */
4346
IMAGE_FRAGMENT: 'superdoc-image-fragment',
@@ -47,6 +50,15 @@ export const DOM_CLASS_NAMES = {
4750

4851
/** Clip wrapper around a cropped inline image. */
4952
INLINE_IMAGE_CLIP_WRAPPER: 'superdoc-inline-image-clip-wrapper',
53+
54+
/** Field annotation outer wrapper. */
55+
ANNOTATION: 'annotation',
56+
57+
/** Field annotation inner content wrapper. */
58+
ANNOTATION_CONTENT: 'annotation-content',
59+
60+
/** Hidden caret anchor span appended after field annotation content. */
61+
ANNOTATION_CARET_ANCHOR: 'annotation-caret-anchor',
5062
} as const;
5163

5264
/** Union of all DOM contract class name values. */

packages/layout-engine/dom-contract/src/data-attrs.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ export const DATA_ATTRS = {
2727

2828
/** JSON-encoded table boundary metadata for resize overlays. */
2929
TABLE_BOUNDARIES: 'data-table-boundaries',
30+
31+
/** SDT unique identifier. */
32+
SDT_ID: 'data-sdt-id',
33+
34+
/** SDT type (fieldAnnotation, structuredContent, documentSection, etc.). */
35+
SDT_TYPE: 'data-sdt-type',
36+
37+
/** Field annotation field identifier. */
38+
FIELD_ID: 'data-field-id',
39+
40+
/** Field annotation field type (signer, text, checkbox, etc.). */
41+
FIELD_TYPE: 'data-field-type',
42+
43+
/** Marks an element as draggable by the editor. */
44+
DRAGGABLE: 'data-draggable',
45+
46+
/** Display label text for drag toast / accessibility. */
47+
DISPLAY_LABEL: 'data-display-label',
48+
49+
/** Field annotation variant (text, image, signature, checkbox, html, link). */
50+
VARIANT: 'data-variant',
51+
52+
/** Element type discriminator (annotation variant, etc.). */
53+
TYPE: 'data-type',
3054
} as const;
3155

3256
/**
@@ -39,4 +63,12 @@ export const DATASET_KEYS = {
3963
PM_END: 'pmEnd',
4064
LAYOUT_EPOCH: 'layoutEpoch',
4165
TABLE_BOUNDARIES: 'tableBoundaries',
66+
SDT_ID: 'sdtId',
67+
SDT_TYPE: 'sdtType',
68+
FIELD_ID: 'fieldId',
69+
FIELD_TYPE: 'fieldType',
70+
DRAGGABLE: 'draggable',
71+
DISPLAY_LABEL: 'displayLabel',
72+
VARIANT: 'variant',
73+
TYPE: 'type',
4274
} as const;

packages/layout-engine/dom-contract/src/index.test.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import {
66
DATASET_KEYS,
77
buildImagePmSelector,
88
buildInlineImagePmSelector,
9+
buildSdtBlockSelector,
10+
buildSdtInlineSelector,
11+
buildAnnotationSelector,
12+
buildAnnotationTypeSelector,
13+
buildAnnotationPmSelector,
14+
SDT_BLOCK_WITH_ID_SELECTOR,
15+
DRAGGABLE_SELECTOR,
916
} from './index.js';
1017

1118
describe('@superdoc/dom-contract', () => {
@@ -18,10 +25,13 @@ describe('@superdoc/dom-contract', () => {
1825
BLOCK_SDT: 'superdoc-structured-content-block',
1926
TABLE_FRAGMENT: 'superdoc-table-fragment',
2027
DOCUMENT_SECTION: 'superdoc-document-section',
21-
SDT_HOVER: 'sdt-hover',
28+
SDT_GROUP_HOVER: 'sdt-group-hover',
2229
IMAGE_FRAGMENT: 'superdoc-image-fragment',
2330
INLINE_IMAGE: 'superdoc-inline-image',
2431
INLINE_IMAGE_CLIP_WRAPPER: 'superdoc-inline-image-clip-wrapper',
32+
ANNOTATION: 'annotation',
33+
ANNOTATION_CONTENT: 'annotation-content',
34+
ANNOTATION_CARET_ANCHOR: 'annotation-caret-anchor',
2535
});
2636
});
2737

@@ -31,13 +41,29 @@ describe('@superdoc/dom-contract', () => {
3141
PM_END: 'data-pm-end',
3242
LAYOUT_EPOCH: 'data-layout-epoch',
3343
TABLE_BOUNDARIES: 'data-table-boundaries',
44+
SDT_ID: 'data-sdt-id',
45+
SDT_TYPE: 'data-sdt-type',
46+
FIELD_ID: 'data-field-id',
47+
FIELD_TYPE: 'data-field-type',
48+
DRAGGABLE: 'data-draggable',
49+
DISPLAY_LABEL: 'data-display-label',
50+
VARIANT: 'data-variant',
51+
TYPE: 'data-type',
3452
});
3553

3654
expect(DATASET_KEYS).toEqual({
3755
PM_START: 'pmStart',
3856
PM_END: 'pmEnd',
3957
LAYOUT_EPOCH: 'layoutEpoch',
4058
TABLE_BOUNDARIES: 'tableBoundaries',
59+
SDT_ID: 'sdtId',
60+
SDT_TYPE: 'sdtType',
61+
FIELD_ID: 'fieldId',
62+
FIELD_TYPE: 'fieldType',
63+
DRAGGABLE: 'draggable',
64+
DISPLAY_LABEL: 'displayLabel',
65+
VARIANT: 'variant',
66+
TYPE: 'type',
4167
});
4268
});
4369

@@ -52,4 +78,32 @@ describe('@superdoc/dom-contract', () => {
5278
'.superdoc-inline-image-clip-wrapper[data-pm-start="99"], .superdoc-inline-image[data-pm-start="99"]',
5379
);
5480
});
81+
82+
it('builds a block SDT selector by escaped id', () => {
83+
expect(buildSdtBlockSelector('abc')).toBe('.superdoc-structured-content-block[data-sdt-id="abc"]');
84+
});
85+
86+
it('builds an inline SDT selector by escaped id', () => {
87+
expect(buildSdtInlineSelector('abc')).toBe('.superdoc-structured-content-inline[data-sdt-id="abc"]');
88+
});
89+
90+
it('builds the annotation selector with pm-start', () => {
91+
expect(buildAnnotationSelector()).toBe('.annotation[data-pm-start]');
92+
});
93+
94+
it('builds an annotation type selector', () => {
95+
expect(buildAnnotationTypeSelector('html')).toBe('.annotation[data-type="html"]');
96+
});
97+
98+
it('builds an annotation pm selector', () => {
99+
expect(buildAnnotationPmSelector(42)).toBe('.annotation[data-pm-start="42"]');
100+
});
101+
102+
it('exports SDT_BLOCK_WITH_ID_SELECTOR constant', () => {
103+
expect(SDT_BLOCK_WITH_ID_SELECTOR).toBe('.superdoc-structured-content-block[data-sdt-id]');
104+
});
105+
106+
it('exports DRAGGABLE_SELECTOR constant', () => {
107+
expect(DRAGGABLE_SELECTOR).toBe('[data-draggable="true"]');
108+
});
55109
});

packages/layout-engine/dom-contract/src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,14 @@ export type { DomClassName } from './class-names.js';
1818

1919
export { DATA_ATTRS, DATASET_KEYS } from './data-attrs.js';
2020

21-
export { buildImagePmSelector, buildInlineImagePmSelector } from './selectors.js';
21+
export {
22+
buildImagePmSelector,
23+
buildInlineImagePmSelector,
24+
buildSdtBlockSelector,
25+
buildSdtInlineSelector,
26+
buildAnnotationSelector,
27+
buildAnnotationTypeSelector,
28+
buildAnnotationPmSelector,
29+
SDT_BLOCK_WITH_ID_SELECTOR,
30+
DRAGGABLE_SELECTOR,
31+
} from './selectors.js';

packages/layout-engine/dom-contract/src/selectors.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { DOM_CLASS_NAMES } from './class-names.js';
22
import { DATA_ATTRS } from './data-attrs.js';
33

4+
// ---------------------------------------------------------------------------
5+
// Stable selector constants
6+
// ---------------------------------------------------------------------------
7+
8+
/** Selector for any block SDT element that carries an sdtId attribute. */
9+
export const SDT_BLOCK_WITH_ID_SELECTOR = `.${DOM_CLASS_NAMES.BLOCK_SDT}[${DATA_ATTRS.SDT_ID}]`;
10+
11+
/** Selector for draggable elements. */
12+
export const DRAGGABLE_SELECTOR = `[${DATA_ATTRS.DRAGGABLE}="true"]`;
13+
414
/**
515
* Builds a compound CSS selector matching any image element (block fragment,
616
* inline clip-wrapper, or bare inline image) by its `data-pm-start` value.
@@ -35,3 +45,51 @@ export function buildInlineImagePmSelector(pmStart: string | number): string {
3545
`.${DOM_CLASS_NAMES.INLINE_IMAGE}[${attr}="${v}"]`,
3646
].join(', ');
3747
}
48+
49+
// ---------------------------------------------------------------------------
50+
// SDT selectors
51+
// ---------------------------------------------------------------------------
52+
53+
/**
54+
* Builds a selector for block-level SDT elements with a given sdtId.
55+
*
56+
* Callers MUST `CSS.escape()` the sdtId before passing it here;
57+
* numeric IDs and pre-escaped strings are safe as-is.
58+
*/
59+
export function buildSdtBlockSelector(escapedSdtId: string): string {
60+
return `.${DOM_CLASS_NAMES.BLOCK_SDT}[${DATA_ATTRS.SDT_ID}="${escapedSdtId}"]`;
61+
}
62+
63+
/**
64+
* Builds a selector for inline SDT wrapper elements with a given sdtId.
65+
*
66+
* Callers MUST `CSS.escape()` the sdtId before passing it here.
67+
*/
68+
export function buildSdtInlineSelector(escapedSdtId: string): string {
69+
return `.${DOM_CLASS_NAMES.INLINE_SDT_WRAPPER}[${DATA_ATTRS.SDT_ID}="${escapedSdtId}"]`;
70+
}
71+
72+
// ---------------------------------------------------------------------------
73+
// Annotation selectors
74+
// ---------------------------------------------------------------------------
75+
76+
/**
77+
* Selector for annotation elements that carry a ProseMirror start position.
78+
*/
79+
export function buildAnnotationSelector(): string {
80+
return `.${DOM_CLASS_NAMES.ANNOTATION}[${DATA_ATTRS.PM_START}]`;
81+
}
82+
83+
/**
84+
* Selector for annotation elements with a specific `data-type` value.
85+
*/
86+
export function buildAnnotationTypeSelector(type: string): string {
87+
return `.${DOM_CLASS_NAMES.ANNOTATION}[${DATA_ATTRS.TYPE}="${type}"]`;
88+
}
89+
90+
/**
91+
* Selector for an annotation element at a specific ProseMirror start position.
92+
*/
93+
export function buildAnnotationPmSelector(pmStart: string | number): string {
94+
return `.${DOM_CLASS_NAMES.ANNOTATION}[${DATA_ATTRS.PM_START}="${String(pmStart)}"]`;
95+
}

0 commit comments

Comments
 (0)