@soeditor/projections 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SoEditor contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @soeditor/projections
2
+
3
+ DOM-free coordination for persistent SoEditor Visual, HTML Source, Markdown,
4
+ and Preview projections. Canonical content remains owned by Core; this package
5
+ only coordinates visibility, one logical primary writer, effective readonly
6
+ state, attachment lifecycle, and shared commands.
7
+
8
+ Installing `ProjectionCoordinatorPlugin` enables coordinated engines. Existing
9
+ engines retain their legacy single-mode behavior when this plugin is absent.
10
+
11
+ Commands:
12
+
13
+ - `projection.activate(id)` transfers primary write authority to one visible,
14
+ attached, format-compatible editable projection;
15
+ - `projection.show(id)` keeps an attached projection visible;
16
+ - `projection.hide(id)` hides a non-primary attached projection.
17
+
18
+ Preview is always readonly and cannot become primary. An editor-level readonly
19
+ policy keeps the logical primary but makes every activity effectively readonly.
@@ -0,0 +1,3 @@
1
+ export { IncompatibleProjectionError, InvalidProjectionTransitionError, ProjectionAlreadyAttachedError, ProjectionCoordinatorPlugin, ProjectionCoordinatorDestroyedError, ProjectionNotAttachedError, projectionCoordinatorServiceToken, } from './projection-coordinator.js';
2
+ export type { EditableProjectionId, ProjectionActivity, ProjectionAdapter, ProjectionCoordinatorService, ProjectionId, ProjectionSnapshot, } from './projection-coordinator.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,2BAA2B,EAC3B,gCAAgC,EAChC,8BAA8B,EAC9B,2BAA2B,EAC3B,mCAAmC,EACnC,0BAA0B,EAC1B,iCAAiC,GACpC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACR,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,4BAA4B,EAC5B,YAAY,EACZ,kBAAkB,GACrB,MAAM,6BAA6B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,351 @@
1
+ import { createServiceToken, Plugin } from "@soeditor/core";
2
+ const projectionCoordinatorServiceToken = createServiceToken(
3
+ "soeditor.projection-coordinator"
4
+ );
5
+ class ProjectionCoordinatorDestroyedError extends Error {
6
+ constructor() {
7
+ super("The projection coordinator has been destroyed.");
8
+ this.name = "ProjectionCoordinatorDestroyedError";
9
+ }
10
+ }
11
+ class ProjectionAlreadyAttachedError extends Error {
12
+ constructor(id) {
13
+ super(`Projection "${id}" is already attached.`);
14
+ this.name = "ProjectionAlreadyAttachedError";
15
+ }
16
+ }
17
+ class ProjectionNotAttachedError extends Error {
18
+ constructor(id) {
19
+ super(`Projection "${id}" is not attached.`);
20
+ this.name = "ProjectionNotAttachedError";
21
+ }
22
+ }
23
+ class IncompatibleProjectionError extends Error {
24
+ constructor(id, format) {
25
+ super(`Projection "${id}" is incompatible with "${format}" documents.`);
26
+ this.name = "IncompatibleProjectionError";
27
+ }
28
+ }
29
+ class InvalidProjectionTransitionError extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = "InvalidProjectionTransitionError";
33
+ }
34
+ }
35
+ const projectionIds = Object.freeze([
36
+ "visual",
37
+ "source",
38
+ "markdown",
39
+ "preview"
40
+ ]);
41
+ class ProjectionCoordinatorPlugin extends Plugin {
42
+ static id = "projection-coordinator";
43
+ #adapters = /* @__PURE__ */ new Map();
44
+ #listeners = /* @__PURE__ */ new Set();
45
+ #visible = /* @__PURE__ */ new Set();
46
+ #destroyed = false;
47
+ #disposeModeChange;
48
+ #disposeStateChange;
49
+ #primary = "visual";
50
+ #synchronizingMode = false;
51
+ init() {
52
+ const format = this.editor.state.document.format;
53
+ this.#primary = defaultPrimary(format);
54
+ const initialMode = this.editor.state.mode;
55
+ if (initialMode === "preview") {
56
+ this.#visible.add(this.#primary);
57
+ this.#visible.add("preview");
58
+ } else if (isEditableForFormat(initialMode, format)) {
59
+ this.#primary = initialMode;
60
+ this.#visible.add(initialMode);
61
+ } else {
62
+ this.#visible.add(this.#primary);
63
+ }
64
+ const getSnapshot = () => this.#snapshot();
65
+ const service = Object.freeze({
66
+ get snapshot() {
67
+ return getSnapshot();
68
+ },
69
+ attach: (adapter) => this.#attach(adapter),
70
+ get: (id) => this.#get(id),
71
+ isAttached: (id) => this.#isAttached(id),
72
+ subscribe: (listener) => this.#subscribe(listener)
73
+ });
74
+ this.editor.services.register(
75
+ projectionCoordinatorServiceToken,
76
+ service
77
+ );
78
+ this.editor.commands.register({
79
+ id: "projection.activate",
80
+ execute: (_context, ...args) => this.#activate(
81
+ readProjectionArgument("projection.activate", args)
82
+ )
83
+ });
84
+ this.editor.commands.register({
85
+ id: "projection.show",
86
+ execute: (_context, ...args) => this.#setVisible(
87
+ readProjectionArgument("projection.show", args),
88
+ true
89
+ )
90
+ });
91
+ this.editor.commands.register({
92
+ id: "projection.hide",
93
+ execute: (_context, ...args) => this.#setVisible(
94
+ readProjectionArgument("projection.hide", args),
95
+ false
96
+ )
97
+ });
98
+ this.#disposeModeChange = this.editor.events.on(
99
+ "mode:change",
100
+ ({ current }) => this.#handleModeChange(current)
101
+ );
102
+ this.#disposeStateChange = this.editor.events.on(
103
+ "state:change",
104
+ ({ current, previous }) => {
105
+ if (current.readonly !== previous.readonly) this.#notify();
106
+ }
107
+ );
108
+ }
109
+ destroy() {
110
+ this.#destroyed = true;
111
+ this.#disposeModeChange?.();
112
+ this.#disposeModeChange = void 0;
113
+ this.#disposeStateChange?.();
114
+ this.#disposeStateChange = void 0;
115
+ this.#adapters.clear();
116
+ this.#listeners.clear();
117
+ this.#visible.clear();
118
+ }
119
+ #attach(adapter) {
120
+ this.#assertAlive();
121
+ validateAdapter(adapter);
122
+ const format = this.editor.state.document.format;
123
+ if (!isCompatible(adapter.id, format)) {
124
+ throw new IncompatibleProjectionError(adapter.id, format);
125
+ }
126
+ if (this.#adapters.has(adapter.id)) {
127
+ throw new ProjectionAlreadyAttachedError(adapter.id);
128
+ }
129
+ this.#adapters.set(adapter.id, adapter);
130
+ try {
131
+ adapter.update(this.#activity(adapter.id));
132
+ } catch (error) {
133
+ this.#adapters.delete(adapter.id);
134
+ throw error;
135
+ }
136
+ let active = true;
137
+ return () => {
138
+ if (!active || this.#destroyed) {
139
+ active = false;
140
+ return;
141
+ }
142
+ if (this.#adapters.get(adapter.id) === adapter) {
143
+ this.#adapters.delete(adapter.id);
144
+ this.#repairPrimaryAfterDetach(adapter.id);
145
+ this.#notify();
146
+ }
147
+ active = false;
148
+ };
149
+ }
150
+ #activate(id) {
151
+ this.#assertAttached(id);
152
+ if (!isEditableForFormat(id, this.editor.state.document.format)) {
153
+ throw new InvalidProjectionTransitionError(
154
+ `Projection "${id}" cannot become a writable primary.`
155
+ );
156
+ }
157
+ if (!this.#visible.has(id)) {
158
+ throw new InvalidProjectionTransitionError(
159
+ `Projection "${id}" must be visible before activation.`
160
+ );
161
+ }
162
+ this.#synchronizingMode = true;
163
+ try {
164
+ if (this.editor.state.mode !== id) {
165
+ this.editor.update((transaction) => transaction.setMode(id), {
166
+ origin: "command"
167
+ });
168
+ }
169
+ } finally {
170
+ this.#synchronizingMode = false;
171
+ if (this.editor.state.mode === id) {
172
+ this.#primary = id;
173
+ this.#notify();
174
+ }
175
+ }
176
+ }
177
+ #setVisible(id, visible) {
178
+ this.#assertAttached(id);
179
+ if (!visible && id === this.#primary) {
180
+ throw new InvalidProjectionTransitionError(
181
+ `Primary projection "${id}" cannot be hidden.`
182
+ );
183
+ }
184
+ const changed = visible ? !this.#visible.has(id) : this.#visible.has(id);
185
+ if (!changed) {
186
+ return;
187
+ }
188
+ if (visible) {
189
+ this.#visible.add(id);
190
+ } else {
191
+ this.#visible.delete(id);
192
+ }
193
+ this.#notify();
194
+ }
195
+ #handleModeChange(mode) {
196
+ if (this.#synchronizingMode) {
197
+ return;
198
+ }
199
+ if (mode === "preview") {
200
+ if (this.#adapters.has("preview")) {
201
+ this.#visible.add("preview");
202
+ this.#notify();
203
+ }
204
+ return;
205
+ }
206
+ if (isEditableForFormat(mode, this.editor.state.document.format) && this.#adapters.has(mode)) {
207
+ this.#visible.add(mode);
208
+ this.#primary = mode;
209
+ this.#notify();
210
+ }
211
+ }
212
+ #repairPrimaryAfterDetach(detached) {
213
+ if (detached !== this.#primary) {
214
+ return;
215
+ }
216
+ const format = this.editor.state.document.format;
217
+ const visible = projectionIds.find(
218
+ (id) => this.#adapters.has(id) && this.#visible.has(id) && isEditableForFormat(id, format)
219
+ );
220
+ const fallback = visible ?? projectionIds.find(
221
+ (id) => this.#adapters.has(id) && isEditableForFormat(id, format)
222
+ );
223
+ this.#primary = fallback ?? defaultPrimary(format);
224
+ if (fallback !== void 0) {
225
+ this.#visible.add(fallback);
226
+ }
227
+ }
228
+ #get(id) {
229
+ this.#assertAlive();
230
+ assertProjectionId(id);
231
+ return this.#activity(id);
232
+ }
233
+ #isAttached(id) {
234
+ this.#assertAlive();
235
+ assertProjectionId(id);
236
+ return this.#adapters.has(id);
237
+ }
238
+ #subscribe(listener) {
239
+ this.#assertAlive();
240
+ if (typeof listener !== "function") {
241
+ throw new TypeError("A projection listener must be a function.");
242
+ }
243
+ this.#listeners.add(listener);
244
+ let active = true;
245
+ return () => {
246
+ if (active) {
247
+ this.#listeners.delete(listener);
248
+ }
249
+ active = false;
250
+ };
251
+ }
252
+ #activity(id) {
253
+ const primary = id === this.#primary;
254
+ return Object.freeze({
255
+ id,
256
+ primary,
257
+ readonly: this.editor.state.readonly || !primary,
258
+ visible: this.#visible.has(id)
259
+ });
260
+ }
261
+ #snapshot() {
262
+ this.#assertAlive();
263
+ return Object.freeze({
264
+ activities: Object.freeze(
265
+ projectionIds.map((id) => this.#activity(id))
266
+ ),
267
+ primary: this.#primary
268
+ });
269
+ }
270
+ #notify() {
271
+ const snapshot = this.#snapshot();
272
+ const errors = [];
273
+ for (const activity of snapshot.activities) {
274
+ const adapter = this.#adapters.get(activity.id);
275
+ if (adapter !== void 0) {
276
+ try {
277
+ adapter.update(activity);
278
+ } catch (error) {
279
+ errors.push(error);
280
+ }
281
+ }
282
+ }
283
+ for (const listener of [...this.#listeners]) {
284
+ try {
285
+ listener(snapshot);
286
+ } catch (error) {
287
+ errors.push(error);
288
+ }
289
+ }
290
+ if (errors.length > 0) {
291
+ throw new AggregateError(
292
+ errors,
293
+ "Projection activity notification failed."
294
+ );
295
+ }
296
+ }
297
+ #assertAttached(id) {
298
+ this.#assertAlive();
299
+ assertProjectionId(id);
300
+ if (!this.#adapters.has(id)) {
301
+ throw new ProjectionNotAttachedError(id);
302
+ }
303
+ }
304
+ #assertAlive() {
305
+ if (this.#destroyed) {
306
+ throw new ProjectionCoordinatorDestroyedError();
307
+ }
308
+ }
309
+ }
310
+ function defaultPrimary(format) {
311
+ return format === "markdown" ? "markdown" : "visual";
312
+ }
313
+ function isCompatible(id, format) {
314
+ return id === "preview" || isEditableForFormat(id, format);
315
+ }
316
+ function isEditableForFormat(id, format) {
317
+ return format === "html" ? id === "visual" || id === "source" : id === "markdown";
318
+ }
319
+ function validateAdapter(adapter) {
320
+ if (typeof adapter !== "object" || adapter === null) {
321
+ throw new TypeError("A projection adapter must be an object.");
322
+ }
323
+ assertProjectionId(adapter.id);
324
+ if (typeof adapter.update !== "function") {
325
+ throw new TypeError(
326
+ `Projection adapter "${adapter.id}" requires an update function.`
327
+ );
328
+ }
329
+ }
330
+ function readProjectionArgument(command, args) {
331
+ if (args.length !== 1) {
332
+ throw new TypeError(`Command "${command}" requires one projection ID.`);
333
+ }
334
+ assertProjectionId(args[0]);
335
+ return args[0];
336
+ }
337
+ function assertProjectionId(value) {
338
+ if (!projectionIds.some((id) => id === value)) {
339
+ throw new TypeError(`Unknown projection ID "${String(value)}".`);
340
+ }
341
+ }
342
+ export {
343
+ IncompatibleProjectionError,
344
+ InvalidProjectionTransitionError,
345
+ ProjectionAlreadyAttachedError,
346
+ ProjectionCoordinatorDestroyedError,
347
+ ProjectionCoordinatorPlugin,
348
+ ProjectionNotAttachedError,
349
+ projectionCoordinatorServiceToken
350
+ };
351
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/projection-coordinator.ts"],"sourcesContent":["import {\n createServiceToken,\n Plugin,\n type DocumentFormat,\n type EditorMode,\n} from '@soeditor/core';\n\n/** Built-in projection identities coordinated in SoEditor 0.6. */\nexport type ProjectionId = 'visual' | 'source' | 'markdown' | 'preview';\n\n/** Projection identities that may hold logical write authority. */\nexport type EditableProjectionId = Exclude<ProjectionId, 'preview'>;\n\n/** Immutable effective activity delivered to one attached projection. */\nexport interface ProjectionActivity {\n readonly id: ProjectionId;\n readonly primary: boolean;\n readonly readonly: boolean;\n readonly visible: boolean;\n}\n\n/** Narrow lifecycle adapter implemented by a surface engine. */\nexport interface ProjectionAdapter {\n readonly id: ProjectionId;\n update(activity: ProjectionActivity): void;\n}\n\n/** Immutable observable coordinator state. */\nexport interface ProjectionSnapshot {\n readonly activities: readonly ProjectionActivity[];\n readonly primary: EditableProjectionId;\n}\n\n/** Per-editor projection attachment and observation capabilities. */\nexport interface ProjectionCoordinatorService {\n readonly snapshot: ProjectionSnapshot;\n attach(adapter: ProjectionAdapter): () => void;\n get(id: ProjectionId): ProjectionActivity;\n isAttached(id: ProjectionId): boolean;\n subscribe(listener: (snapshot: ProjectionSnapshot) => void): () => void;\n}\n\n/** Typed identity of the per-editor projection coordinator. */\nexport const projectionCoordinatorServiceToken =\n createServiceToken<ProjectionCoordinatorService>(\n 'soeditor.projection-coordinator',\n );\n\nexport class ProjectionCoordinatorDestroyedError extends Error {\n constructor() {\n super('The projection coordinator has been destroyed.');\n this.name = 'ProjectionCoordinatorDestroyedError';\n }\n}\n\nexport class ProjectionAlreadyAttachedError extends Error {\n constructor(id: ProjectionId) {\n super(`Projection \"${id}\" is already attached.`);\n this.name = 'ProjectionAlreadyAttachedError';\n }\n}\n\nexport class ProjectionNotAttachedError extends Error {\n constructor(id: ProjectionId) {\n super(`Projection \"${id}\" is not attached.`);\n this.name = 'ProjectionNotAttachedError';\n }\n}\n\nexport class IncompatibleProjectionError extends Error {\n constructor(id: ProjectionId, format: DocumentFormat) {\n super(`Projection \"${id}\" is incompatible with \"${format}\" documents.`);\n this.name = 'IncompatibleProjectionError';\n }\n}\n\nexport class InvalidProjectionTransitionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidProjectionTransitionError';\n }\n}\n\nconst projectionIds: readonly ProjectionId[] = Object.freeze([\n 'visual',\n 'source',\n 'markdown',\n 'preview',\n]);\n\n/** Coordinates persistent projection activity without owning surface DOM. */\nexport class ProjectionCoordinatorPlugin extends Plugin {\n static readonly id = 'projection-coordinator';\n\n readonly #adapters = new Map<ProjectionId, ProjectionAdapter>();\n readonly #listeners = new Set<(snapshot: ProjectionSnapshot) => void>();\n readonly #visible = new Set<ProjectionId>();\n #destroyed = false;\n #disposeModeChange: (() => void) | undefined;\n #disposeStateChange: (() => void) | undefined;\n #primary: EditableProjectionId = 'visual';\n #synchronizingMode = false;\n\n override init(): void {\n const format = this.editor.state.document.format;\n this.#primary = defaultPrimary(format);\n const initialMode = this.editor.state.mode;\n if (initialMode === 'preview') {\n this.#visible.add(this.#primary);\n this.#visible.add('preview');\n } else if (isEditableForFormat(initialMode, format)) {\n this.#primary = initialMode;\n this.#visible.add(initialMode);\n } else {\n this.#visible.add(this.#primary);\n }\n\n const getSnapshot = (): ProjectionSnapshot => this.#snapshot();\n const service = Object.freeze<ProjectionCoordinatorService>({\n get snapshot() {\n return getSnapshot();\n },\n attach: (adapter) => this.#attach(adapter),\n get: (id) => this.#get(id),\n isAttached: (id) => this.#isAttached(id),\n subscribe: (listener) => this.#subscribe(listener),\n });\n this.editor.services.register(\n projectionCoordinatorServiceToken,\n service,\n );\n this.editor.commands.register({\n id: 'projection.activate',\n execute: (_context, ...args) =>\n this.#activate(\n readProjectionArgument('projection.activate', args),\n ),\n });\n this.editor.commands.register({\n id: 'projection.show',\n execute: (_context, ...args) =>\n this.#setVisible(\n readProjectionArgument('projection.show', args),\n true,\n ),\n });\n this.editor.commands.register({\n id: 'projection.hide',\n execute: (_context, ...args) =>\n this.#setVisible(\n readProjectionArgument('projection.hide', args),\n false,\n ),\n });\n this.#disposeModeChange = this.editor.events.on(\n 'mode:change',\n ({ current }) => this.#handleModeChange(current),\n );\n this.#disposeStateChange = this.editor.events.on(\n 'state:change',\n ({ current, previous }) => {\n if (current.readonly !== previous.readonly) this.#notify();\n },\n );\n }\n\n override destroy(): void {\n this.#destroyed = true;\n this.#disposeModeChange?.();\n this.#disposeModeChange = undefined;\n this.#disposeStateChange?.();\n this.#disposeStateChange = undefined;\n this.#adapters.clear();\n this.#listeners.clear();\n this.#visible.clear();\n }\n\n #attach(adapter: ProjectionAdapter): () => void {\n this.#assertAlive();\n validateAdapter(adapter);\n const format = this.editor.state.document.format;\n if (!isCompatible(adapter.id, format)) {\n throw new IncompatibleProjectionError(adapter.id, format);\n }\n if (this.#adapters.has(adapter.id)) {\n throw new ProjectionAlreadyAttachedError(adapter.id);\n }\n this.#adapters.set(adapter.id, adapter);\n try {\n adapter.update(this.#activity(adapter.id));\n } catch (error: unknown) {\n this.#adapters.delete(adapter.id);\n throw error;\n }\n let active = true;\n return () => {\n if (!active || this.#destroyed) {\n active = false;\n return;\n }\n if (this.#adapters.get(adapter.id) === adapter) {\n this.#adapters.delete(adapter.id);\n this.#repairPrimaryAfterDetach(adapter.id);\n this.#notify();\n }\n active = false;\n };\n }\n\n #activate(id: ProjectionId): void {\n this.#assertAttached(id);\n if (!isEditableForFormat(id, this.editor.state.document.format)) {\n throw new InvalidProjectionTransitionError(\n `Projection \"${id}\" cannot become a writable primary.`,\n );\n }\n if (!this.#visible.has(id)) {\n throw new InvalidProjectionTransitionError(\n `Projection \"${id}\" must be visible before activation.`,\n );\n }\n this.#synchronizingMode = true;\n try {\n if (this.editor.state.mode !== id) {\n this.editor.update((transaction) => transaction.setMode(id), {\n origin: 'command',\n });\n }\n } finally {\n this.#synchronizingMode = false;\n if (this.editor.state.mode === id) {\n this.#primary = id;\n this.#notify();\n }\n }\n }\n\n #setVisible(id: ProjectionId, visible: boolean): void {\n this.#assertAttached(id);\n if (!visible && id === this.#primary) {\n throw new InvalidProjectionTransitionError(\n `Primary projection \"${id}\" cannot be hidden.`,\n );\n }\n const changed = visible\n ? !this.#visible.has(id)\n : this.#visible.has(id);\n if (!changed) {\n return;\n }\n if (visible) {\n this.#visible.add(id);\n } else {\n this.#visible.delete(id);\n }\n this.#notify();\n }\n\n #handleModeChange(mode: EditorMode): void {\n if (this.#synchronizingMode) {\n return;\n }\n if (mode === 'preview') {\n if (this.#adapters.has('preview')) {\n this.#visible.add('preview');\n this.#notify();\n }\n return;\n }\n if (\n isEditableForFormat(mode, this.editor.state.document.format) &&\n this.#adapters.has(mode)\n ) {\n this.#visible.add(mode);\n this.#primary = mode;\n this.#notify();\n }\n }\n\n #repairPrimaryAfterDetach(detached: ProjectionId): void {\n if (detached !== this.#primary) {\n return;\n }\n const format = this.editor.state.document.format;\n const visible = projectionIds.find(\n (id): id is EditableProjectionId =>\n this.#adapters.has(id) &&\n this.#visible.has(id) &&\n isEditableForFormat(id, format),\n );\n const fallback =\n visible ??\n projectionIds.find(\n (id): id is EditableProjectionId =>\n this.#adapters.has(id) && isEditableForFormat(id, format),\n );\n this.#primary = fallback ?? defaultPrimary(format);\n if (fallback !== undefined) {\n this.#visible.add(fallback);\n }\n }\n\n #get(id: ProjectionId): ProjectionActivity {\n this.#assertAlive();\n assertProjectionId(id);\n return this.#activity(id);\n }\n\n #isAttached(id: ProjectionId): boolean {\n this.#assertAlive();\n assertProjectionId(id);\n return this.#adapters.has(id);\n }\n\n #subscribe(listener: (snapshot: ProjectionSnapshot) => void): () => void {\n this.#assertAlive();\n if (typeof listener !== 'function') {\n throw new TypeError('A projection listener must be a function.');\n }\n this.#listeners.add(listener);\n let active = true;\n return () => {\n if (active) {\n this.#listeners.delete(listener);\n }\n active = false;\n };\n }\n\n #activity(id: ProjectionId): ProjectionActivity {\n const primary = id === this.#primary;\n return Object.freeze({\n id,\n primary,\n readonly: this.editor.state.readonly || !primary,\n visible: this.#visible.has(id),\n });\n }\n\n #snapshot(): ProjectionSnapshot {\n this.#assertAlive();\n return Object.freeze({\n activities: Object.freeze(\n projectionIds.map((id) => this.#activity(id)),\n ),\n primary: this.#primary,\n });\n }\n\n #notify(): void {\n const snapshot = this.#snapshot();\n const errors: unknown[] = [];\n for (const activity of snapshot.activities) {\n const adapter = this.#adapters.get(activity.id);\n if (adapter !== undefined) {\n try {\n adapter.update(activity);\n } catch (error: unknown) {\n errors.push(error);\n }\n }\n }\n for (const listener of [...this.#listeners]) {\n try {\n listener(snapshot);\n } catch (error: unknown) {\n errors.push(error);\n }\n }\n if (errors.length > 0) {\n throw new AggregateError(\n errors,\n 'Projection activity notification failed.',\n );\n }\n }\n\n #assertAttached(id: ProjectionId): void {\n this.#assertAlive();\n assertProjectionId(id);\n if (!this.#adapters.has(id)) {\n throw new ProjectionNotAttachedError(id);\n }\n }\n\n #assertAlive(): void {\n if (this.#destroyed) {\n throw new ProjectionCoordinatorDestroyedError();\n }\n }\n}\n\nfunction defaultPrimary(format: DocumentFormat): EditableProjectionId {\n return format === 'markdown' ? 'markdown' : 'visual';\n}\n\nfunction isCompatible(id: ProjectionId, format: DocumentFormat): boolean {\n return id === 'preview' || isEditableForFormat(id, format);\n}\n\nfunction isEditableForFormat(\n id: EditorMode,\n format: DocumentFormat,\n): id is EditableProjectionId {\n return format === 'html'\n ? id === 'visual' || id === 'source'\n : id === 'markdown';\n}\n\nfunction validateAdapter(adapter: ProjectionAdapter): void {\n if (typeof adapter !== 'object' || adapter === null) {\n throw new TypeError('A projection adapter must be an object.');\n }\n assertProjectionId(adapter.id);\n if (typeof adapter.update !== 'function') {\n throw new TypeError(\n `Projection adapter \"${adapter.id}\" requires an update function.`,\n );\n }\n}\n\nfunction readProjectionArgument(\n command: string,\n args: readonly unknown[],\n): ProjectionId {\n if (args.length !== 1) {\n throw new TypeError(`Command \"${command}\" requires one projection ID.`);\n }\n assertProjectionId(args[0]);\n return args[0];\n}\n\nfunction assertProjectionId(value: unknown): asserts value is ProjectionId {\n if (!projectionIds.some((id) => id === value)) {\n throw new TypeError(`Unknown projection ID \"${String(value)}\".`);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { Plugin, type DocumentFormat } from '@soeditor/core';
2
+ /** Built-in projection identities coordinated in SoEditor 0.6. */
3
+ export type ProjectionId = 'visual' | 'source' | 'markdown' | 'preview';
4
+ /** Projection identities that may hold logical write authority. */
5
+ export type EditableProjectionId = Exclude<ProjectionId, 'preview'>;
6
+ /** Immutable effective activity delivered to one attached projection. */
7
+ export interface ProjectionActivity {
8
+ readonly id: ProjectionId;
9
+ readonly primary: boolean;
10
+ readonly readonly: boolean;
11
+ readonly visible: boolean;
12
+ }
13
+ /** Narrow lifecycle adapter implemented by a surface engine. */
14
+ export interface ProjectionAdapter {
15
+ readonly id: ProjectionId;
16
+ update(activity: ProjectionActivity): void;
17
+ }
18
+ /** Immutable observable coordinator state. */
19
+ export interface ProjectionSnapshot {
20
+ readonly activities: readonly ProjectionActivity[];
21
+ readonly primary: EditableProjectionId;
22
+ }
23
+ /** Per-editor projection attachment and observation capabilities. */
24
+ export interface ProjectionCoordinatorService {
25
+ readonly snapshot: ProjectionSnapshot;
26
+ attach(adapter: ProjectionAdapter): () => void;
27
+ get(id: ProjectionId): ProjectionActivity;
28
+ isAttached(id: ProjectionId): boolean;
29
+ subscribe(listener: (snapshot: ProjectionSnapshot) => void): () => void;
30
+ }
31
+ /** Typed identity of the per-editor projection coordinator. */
32
+ export declare const projectionCoordinatorServiceToken: import("@soeditor/core").ServiceToken<ProjectionCoordinatorService>;
33
+ export declare class ProjectionCoordinatorDestroyedError extends Error {
34
+ constructor();
35
+ }
36
+ export declare class ProjectionAlreadyAttachedError extends Error {
37
+ constructor(id: ProjectionId);
38
+ }
39
+ export declare class ProjectionNotAttachedError extends Error {
40
+ constructor(id: ProjectionId);
41
+ }
42
+ export declare class IncompatibleProjectionError extends Error {
43
+ constructor(id: ProjectionId, format: DocumentFormat);
44
+ }
45
+ export declare class InvalidProjectionTransitionError extends Error {
46
+ constructor(message: string);
47
+ }
48
+ /** Coordinates persistent projection activity without owning surface DOM. */
49
+ export declare class ProjectionCoordinatorPlugin extends Plugin {
50
+ #private;
51
+ static readonly id = "projection-coordinator";
52
+ init(): void;
53
+ destroy(): void;
54
+ }
55
+ //# sourceMappingURL=projection-coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection-coordinator.d.ts","sourceRoot":"","sources":["../src/projection-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,MAAM,EACN,KAAK,cAAc,EAEtB,MAAM,gBAAgB,CAAC;AAExB,kEAAkE;AAClE,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAExE,mEAAmE;AACnE,MAAM,MAAM,oBAAoB,GAAG,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAEpE,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC7B;AAED,gEAAgE;AAChE,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAC9C;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;CAC1C;AAED,qEAAqE;AACrE,MAAM,WAAW,4BAA4B;IACzC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;IAC/C,GAAG,CAAC,EAAE,EAAE,YAAY,GAAG,kBAAkB,CAAC;IAC1C,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC;IACtC,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC3E;AAED,+DAA+D;AAC/D,eAAO,MAAM,iCAAiC,qEAGzC,CAAC;AAEN,qBAAa,mCAAoC,SAAQ,KAAK;;CAK7D;AAED,qBAAa,8BAA+B,SAAQ,KAAK;gBACzC,EAAE,EAAE,YAAY;CAI/B;AAED,qBAAa,0BAA2B,SAAQ,KAAK;gBACrC,EAAE,EAAE,YAAY;CAI/B;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACtC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc;CAIvD;AAED,qBAAa,gCAAiC,SAAQ,KAAK;gBAC3C,OAAO,EAAE,MAAM;CAI9B;AASD,6EAA6E;AAC7E,qBAAa,2BAA4B,SAAQ,MAAM;;IACnD,MAAM,CAAC,QAAQ,CAAC,EAAE,4BAA4B;IAWrC,IAAI,IAAI,IAAI;IA+DZ,OAAO,IAAI,IAAI;CAgO3B"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@soeditor/projections",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "description": "Framework-independent projection activity coordination for SoEditor.",
6
+ "keywords": [
7
+ "content-editor",
8
+ "projection",
9
+ "typescript"
10
+ ],
11
+ "homepage": "https://github.com/sohophp/soeditor#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/sohophp/soeditor/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/sohophp/soeditor.git",
18
+ "directory": "packages/projections"
19
+ },
20
+ "engines": {
21
+ "node": ">=22.14.0 <23"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "provenance": true,
26
+ "registry": "https://registry.npmjs.org/"
27
+ },
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ }
38
+ },
39
+ "peerDependencies": {
40
+ "@soeditor/core": "1.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@soeditor/core": "1.0.0"
44
+ },
45
+ "scripts": {
46
+ "build": "vite build && tsc -p tsconfig.build.json",
47
+ "test": "vitest run",
48
+ "typecheck": "tsc -p tsconfig.json"
49
+ }
50
+ }