| 66 | // ─── Implementation ─────────────────────────────────────────────────────────── |
| 67 | |
| 68 | class CompositionImpl implements Composition { |
| 69 | private readonly parsed: ParsedDocument; |
| 70 | private readonly persist: PersistAdapter | undefined; |
| 71 | readonly preview: PreviewAdapter | undefined; |
| 72 | |
| 73 | /** Accumulated override-set — T3 embedded mode fold contract. */ |
| 74 | private overrides: OverrideSet; |
| 75 | |
| 76 | /** Lazily-built element snapshot, invalidated on every mutation. */ |
| 77 | private elementsCache: ElementSnapshot[] | null = null; |
| 78 | |
| 79 | private currentSelection: string[] = []; |
| 80 | |
| 81 | private changeHandlers: Array<() => void> = []; |
| 82 | private selectionHandlers: Array<(ids: string[]) => void> = []; |
| 83 | private patchHandlers: Array<(e: PatchEvent) => void> = []; |
| 84 | private errorHandlers: Array<(e: PersistErrorEvent) => void> = []; |
| 85 | private previewSelectionUnsubscribe: (() => void) | null = null; |
| 86 | |
| 87 | /** Attached by openComposition() for standalone mode. */ |
| 88 | private historyModule: HistoryModule | null = null; |
| 89 | private persistQueueModule: PersistQueueModule | null = null; |
| 90 | |
| 91 | /** Batching state: accumulates patches from multiple dispatches. */ |
| 92 | private batchDepth = 0; |
| 93 | private batchForward: JsonPatchOp[] = []; |
| 94 | private batchInverse: JsonPatchOp[] = []; |
| 95 | private batchOpTypes: string[] = []; |
| 96 | private batchOrigin: unknown = ORIGIN_LOCAL; |
| 97 | /** Override-set state at outermost batch entry — restored if the batch throws. */ |
| 98 | private batchOverridesSnapshot: OverrideSet = {}; |
| 99 | |
| 100 | constructor(parsed: ParsedDocument, opts: OpenCompositionOptions) { |
| 101 | this.parsed = parsed; |
| 102 | this.persist = opts.persist; |
| 103 | this.preview = opts.preview; |
| 104 | this.overrides = { ...(opts.overrides ?? {}) }; |
| 105 | this.previewSelectionUnsubscribe = |
| 106 | this.preview?.on("selection", (ids) => this.updateSelection(ids)) ?? null; |
| 107 | } |
| 108 | |
| 109 | attachHistory(module: HistoryModule): void { |
| 110 | this.historyModule = module; |
| 111 | } |
| 112 | |
| 113 | attachPersistQueue(module: PersistQueueModule): void { |
| 114 | this.persistQueueModule = module; |
| 115 | } |
| 116 | |
| 117 | _fireError(e: PersistErrorEvent): void { |
| 118 | this.errorHandlers.forEach((h) => h(e)); |
| 119 | } |
| 120 | |
| 121 | // ── Typed methods (F10 layer 1) ───────────────────────────────────────────── |
| 122 | |
| 123 | setStyle(id: HfId, styles: Record<string, string | null>): void { |
| 124 | this.dispatch({ type: "setStyle", target: id, styles }); |
| 125 | } |
nothing calls this directly
no outgoing calls
no test coverage detected