Skip to content

Commit 2fb1f83

Browse files
committed
docs(structured-content): add lock mode documentation
Document the w:lock support for structured content nodes: - Add lockMode attribute and lock modes section to extension docs - Add StructuredContentLockMode type definition - Add code examples for inserting and updating lock modes - Update interactive demo with lock/unlock buttons - Update template builder docs with lockMode in field definitions
1 parent 77ff6dc commit 2fb1f83

4 files changed

Lines changed: 300 additions & 18 deletions

File tree

apps/docs/extensions/structured-content.mdx

Lines changed: 221 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,138 @@ Node attributes that can be set and retrieved:
4646
Display name for the block
4747
</ParamField>
4848

49+
<ParamField path="lockMode" type="StructuredContentLockMode" default="unlocked">
50+
Controls editing and deletion restrictions on the structured content node. See [Lock modes](#lock-modes) below.
51+
</ParamField>
52+
53+
## Lock modes
54+
55+
Structured content nodes support four lock modes based on the OOXML [`w:lock` element](https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.lock) (ISO/IEC 29500 §17.5.2.23). Lock modes control whether users can edit the content inside a field and whether they can delete the field wrapper itself.
56+
57+
| Lock mode | Wrapper | Content | Use case |
58+
|-----------|---------|---------|----------|
59+
| `unlocked` | Deletable | Editable | Default — no restrictions |
60+
| `sdtLocked` | Protected | Editable | Protect field structure, allow value changes |
61+
| `contentLocked` | Deletable | Read-only | Display a computed value users can remove |
62+
| `sdtContentLocked` | Protected | Read-only | Fully protected field (e.g., system-generated ID) |
63+
64+
Set the lock mode when inserting:
65+
66+
<CodeGroup>
67+
```javascript Usage
68+
editor.commands.insertStructuredContentInline({
69+
attrs: {
70+
id: '1',
71+
alias: 'Customer Name',
72+
lockMode: 'sdtLocked',
73+
},
74+
text: 'John Doe',
75+
});
76+
```
77+
78+
```javascript Full Example
79+
import { SuperDoc } from 'superdoc';
80+
import 'superdoc/style.css';
81+
82+
const superdoc = new SuperDoc({
83+
selector: '#editor',
84+
document: yourFile,
85+
onReady: (superdoc) => {
86+
const editor = superdoc.activeEditor;
87+
editor.commands.insertStructuredContentInline({
88+
attrs: {
89+
id: '1',
90+
alias: 'Customer Name',
91+
lockMode: 'sdtLocked',
92+
},
93+
text: 'John Doe',
94+
});
95+
},
96+
});
97+
```
98+
</CodeGroup>
99+
100+
Change the lock mode on an existing field:
101+
102+
<CodeGroup>
103+
```javascript Usage
104+
editor.commands.updateStructuredContentById('1', {
105+
attrs: { lockMode: 'contentLocked' },
106+
});
107+
```
108+
109+
```javascript Full Example
110+
import { SuperDoc } from 'superdoc';
111+
import 'superdoc/style.css';
112+
113+
const superdoc = new SuperDoc({
114+
selector: '#editor',
115+
document: yourFile,
116+
onReady: (superdoc) => {
117+
const editor = superdoc.activeEditor;
118+
editor.commands.updateStructuredContentById('1', {
119+
attrs: { lockMode: 'contentLocked' },
120+
});
121+
},
122+
});
123+
```
124+
</CodeGroup>
125+
126+
Lock modes are enforced at the editor plugin level using a three-layer defense:
127+
128+
1. **Key interception** — Delete, Backspace, and Cut are blocked before a transaction is created, preventing cursor jumps
129+
2. **Text input blocking** — Typing is silently blocked in content-locked nodes
130+
3. **Transaction filter** — Safety net that catches paste, drag-drop, and programmatic edits
131+
132+
Users can still place their cursor inside locked content and select text for copying. Only modifications are blocked.
133+
134+
<Info>
135+
Lock modes round-trip through DOCX. A document with `w:lock` elements in
136+
its SDT properties will import with the correct lock mode and export the
137+
`w:lock` element back to the saved file.
138+
</Info>
139+
49140
## Commands
50141

51142
### `insertStructuredContentInline`
52143

53-
Inserts a structured content inline at selection.
144+
Inserts a structured content inline at the current selection.
145+
146+
**Example:**
147+
148+
<CodeGroup>
149+
```javascript Usage
150+
editor.commands.insertStructuredContentInline({
151+
attrs: {
152+
id: '1',
153+
alias: 'Customer Name',
154+
lockMode: 'sdtLocked', // optional, defaults to 'unlocked'
155+
},
156+
text: 'John Doe',
157+
});
158+
```
159+
160+
```javascript Full Example
161+
import { SuperDoc } from 'superdoc';
162+
import 'superdoc/style.css';
163+
164+
const superdoc = new SuperDoc({
165+
selector: '#editor',
166+
document: yourFile,
167+
onReady: (superdoc) => {
168+
const editor = superdoc.activeEditor;
169+
editor.commands.insertStructuredContentInline({
170+
attrs: {
171+
id: '1',
172+
alias: 'Customer Name',
173+
lockMode: 'sdtLocked',
174+
},
175+
text: 'John Doe',
176+
});
177+
},
178+
});
179+
```
180+
</CodeGroup>
54181

55182
**Parameters:**
56183

@@ -62,7 +189,43 @@ Inserts a structured content inline at selection.
62189

63190
### `insertStructuredContentBlock`
64191

65-
Inserts a structured content block at selection.
192+
Inserts a structured content block at the current selection.
193+
194+
**Example:**
195+
196+
<CodeGroup>
197+
```javascript Usage
198+
editor.commands.insertStructuredContentBlock({
199+
attrs: {
200+
id: '2',
201+
alias: 'Terms Section',
202+
lockMode: 'sdtContentLocked', // optional, defaults to 'unlocked'
203+
},
204+
html: '<p>These terms are non-negotiable.</p>',
205+
});
206+
```
207+
208+
```javascript Full Example
209+
import { SuperDoc } from 'superdoc';
210+
import 'superdoc/style.css';
211+
212+
const superdoc = new SuperDoc({
213+
selector: '#editor',
214+
document: yourFile,
215+
onReady: (superdoc) => {
216+
const editor = superdoc.activeEditor;
217+
editor.commands.insertStructuredContentBlock({
218+
attrs: {
219+
id: '2',
220+
alias: 'Terms Section',
221+
lockMode: 'sdtContentLocked',
222+
},
223+
html: '<p>These terms are non-negotiable.</p>',
224+
});
225+
},
226+
});
227+
```
228+
</CodeGroup>
66229

67230
**Parameters:**
68231

@@ -78,6 +241,53 @@ Updates a single structured content field by its unique ID.
78241
IDs are unique identifiers, so this will update at most one field.
79242
If the updated node does not match the schema, it will not be updated.
80243

244+
Pass `attrs` alone to change attributes (like `lockMode`) without replacing content:
245+
246+
**Example:**
247+
248+
<CodeGroup>
249+
```javascript Usage
250+
// Update content
251+
editor.commands.updateStructuredContentById('1', {
252+
text: 'Jane Smith',
253+
});
254+
255+
// Update lock mode only (preserves content)
256+
editor.commands.updateStructuredContentById('1', {
257+
attrs: { lockMode: 'contentLocked' },
258+
});
259+
260+
// Update both content and attributes
261+
editor.commands.updateStructuredContentById('1', {
262+
text: 'New value',
263+
attrs: { alias: 'Updated Label', lockMode: 'sdtLocked' },
264+
});
265+
```
266+
267+
```javascript Full Example
268+
import { SuperDoc } from 'superdoc';
269+
import 'superdoc/style.css';
270+
271+
const superdoc = new SuperDoc({
272+
selector: '#editor',
273+
document: yourFile,
274+
onReady: (superdoc) => {
275+
const editor = superdoc.activeEditor;
276+
277+
// Update content
278+
editor.commands.updateStructuredContentById('1', {
279+
text: 'Jane Smith',
280+
});
281+
282+
// Update lock mode only (preserves content)
283+
editor.commands.updateStructuredContentById('1', {
284+
attrs: { lockMode: 'contentLocked' },
285+
});
286+
},
287+
});
288+
```
289+
</CodeGroup>
290+
81291
**Parameters:**
82292

83293
<ParamField path="id" type="string" required>
@@ -456,6 +666,12 @@ const superdoc = new SuperDoc({
456666

457667
## Types
458668

669+
### `StructuredContentLockMode`
670+
671+
```typescript
672+
type StructuredContentLockMode = 'unlocked' | 'sdtLocked' | 'contentLocked' | 'sdtContentLocked'
673+
```
674+
459675
### `StructuredContentInlineInsert`
460676
461677
<Expandable title="Properties">
@@ -466,7 +682,7 @@ const superdoc = new SuperDoc({
466682
ProseMirror JSON
467683
</ResponseField>
468684
<ResponseField name="attrs" type="Object">
469-
Node attributes
685+
Node attributes (`id`, `alias`, `tag`, `lockMode`, `group`)
470686
</ResponseField>
471687
</Expandable>
472688
@@ -480,7 +696,7 @@ const superdoc = new SuperDoc({
480696
ProseMirror JSON
481697
</ResponseField>
482698
<ResponseField name="attrs" type="Object">
483-
Node attributes
699+
Node attributes (`id`, `alias`, `tag`, `lockMode`)
484700
</ResponseField>
485701
</Expandable>
486702
@@ -497,7 +713,7 @@ const superdoc = new SuperDoc({
497713
Replace content with ProseMirror JSON (overrides html)
498714
</ResponseField>
499715
<ResponseField name="attrs" type="Object">
500-
Update attributes only (preserves content)
716+
Update attributes only (preserves content). Supports `lockMode`, `alias`, `tag`.
501717
</ResponseField>
502718
</Expandable>
503719

apps/docs/snippets/extensions/structured-content.mdx

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ Native Word SDT (w:sdt) fields for documents. Supports inline and block structur
22

33
## Use case
44

5-
- Form templates - Create fillable documents with inline text fields and block content areas
6-
- Contract generation - Dynamic clauses and terms that map to Word content controls
7-
- Document automation - Programmatically update specific sections while preserving structure
5+
- Form templates — Create fillable documents with inline text fields and block content areas
6+
- Contract generation — Dynamic clauses and terms that map to Word content controls
7+
- Document automation — Programmatically update specific sections while preserving structure
8+
- Protected fields — Lock modes control which fields users can edit or delete (ECMA-376 `w:lock`)
89

910
import { SuperDocEditor } from '/snippets/components/superdoc-editor.jsx'
1011

@@ -29,6 +30,22 @@ people will never forget how you made them feel."</i><br/><br/><b>Maya Angelou</
2930
});
3031
}
3132
},
33+
{
34+
label: 'Insert locked field',
35+
onClick: (superdoc) => {
36+
const editor = superdoc?.activeEditor || superdoc?.editor
37+
if (!editor?.commands) return
38+
39+
editor.commands.insertStructuredContentInline({
40+
attrs: {
41+
id: '3',
42+
alias: 'Account ID',
43+
lockMode: 'sdtContentLocked',
44+
},
45+
text: 'ACC-00042',
46+
});
47+
}
48+
},
3249
{
3350
label: 'Insert block field',
3451
onClick: (superdoc) => {
@@ -39,6 +56,7 @@ people will never forget how you made them feel."</i><br/><br/><b>Maya Angelou</
3956
attrs: {
4057
id: '2',
4158
alias: 'Terms & Conditions',
59+
lockMode: 'sdtLocked',
4260
},
4361
json: { type: 'paragraph', content: [{ type: 'text', text: 'Legal content...' }] }
4462
});
@@ -54,13 +72,24 @@ people will never forget how you made them feel."</i><br/><br/><b>Maya Angelou</
5472
}
5573
},
5674
{
57-
label: 'Update block field',
75+
label: 'Lock inline field',
5876
onClick: (superdoc) => {
5977
const editor = superdoc?.activeEditor || superdoc?.editor
6078
if (!editor?.commands) return
6179

62-
editor.commands.updateStructuredContentById('2', {
63-
json: { type: 'paragraph', content: [{ type: 'text', text: 'Updated legal content...' }] }
80+
editor.commands.updateStructuredContentById('1', {
81+
attrs: { lockMode: 'contentLocked' },
82+
});
83+
}
84+
},
85+
{
86+
label: 'Unlock inline field',
87+
onClick: (superdoc) => {
88+
const editor = superdoc?.activeEditor || superdoc?.editor
89+
if (!editor?.commands) return
90+
91+
editor.commands.updateStructuredContentById('1', {
92+
attrs: { lockMode: 'unlocked' },
6493
});
6594
}
6695
},
@@ -85,29 +114,39 @@ people will never forget how you made them feel."</i><br/><br/><b>Maya Angelou</
85114
editor.commands.insertStructuredContentInline({
86115
attrs: {
87116
id: '1',
88-
alias: 'Customer Name'
117+
alias: 'Customer Name',
118+
lockMode: 'sdtLocked', // users can edit the value but not delete the field
89119
},
90120
text: 'John Doe'
91121
});
92122

123+
// Insert a read-only system field
124+
editor.commands.insertStructuredContentInline({
125+
attrs: {
126+
id: '3',
127+
alias: 'Account ID',
128+
lockMode: 'sdtContentLocked', // fully protected — no edits, no deletion
129+
},
130+
text: 'ACC-00042'
131+
});
132+
93133
// Insert block field for terms section
94134
editor.commands.insertStructuredContentBlock({
95135
attrs: {
96136
id: '2',
97-
alias: 'Terms & Conditions'
137+
alias: 'Terms & Conditions',
98138
},
99139
html: '<p>Please review the terms...</p>'
100140
});
101141

102-
// Update inline field content
142+
// Update field content
103143
editor.commands.updateStructuredContentById('1', {
104144
text: 'Jane Smith'
105145
});
106146

107-
// Update block field content and attributes
108-
editor.commands.updateStructuredContentById('2', {
109-
html: '<p>Updated terms and conditions...</p>',
110-
attrs: { alias: 'Legal Terms' }
147+
// Change lock mode without changing content
148+
editor.commands.updateStructuredContentById('1', {
149+
attrs: { lockMode: 'contentLocked' }
111150
});
112151

113152
// Get all structured content tags

0 commit comments

Comments
 (0)