@aethervtt/shard-audio-editor 0.0.0-stage → 0.2.1

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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/dom.d.ts +6 -0
  4. package/dist/dom.js +11 -0
  5. package/dist/drawing.d.ts +2 -0
  6. package/dist/drawing.js +12 -0
  7. package/dist/icons.d.ts +1 -0
  8. package/dist/icons.js +29 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/memory.d.ts +13 -0
  12. package/dist/memory.js +93 -0
  13. package/dist/methods.d.ts +4 -0
  14. package/dist/methods.js +79 -0
  15. package/dist/model.d.ts +61 -0
  16. package/dist/model.js +560 -0
  17. package/dist/panels/common.d.ts +33 -0
  18. package/dist/panels/common.js +1556 -0
  19. package/dist/panels/diagnostics.d.ts +2 -0
  20. package/dist/panels/diagnostics.js +7 -0
  21. package/dist/panels/event.d.ts +2 -0
  22. package/dist/panels/event.js +7 -0
  23. package/dist/panels/library.d.ts +2 -0
  24. package/dist/panels/library.js +7 -0
  25. package/dist/panels/mixer.d.ts +2 -0
  26. package/dist/panels/mixer.js +7 -0
  27. package/dist/panels/music.d.ts +2 -0
  28. package/dist/panels/music.js +7 -0
  29. package/dist/panels/parameters.d.ts +2 -0
  30. package/dist/panels/parameters.js +7 -0
  31. package/dist/preview-export.d.ts +68 -0
  32. package/dist/preview-export.js +143 -0
  33. package/dist/preview-render.d.ts +2 -0
  34. package/dist/preview-render.js +6 -0
  35. package/dist/preview-runtime.d.ts +114 -0
  36. package/dist/preview-runtime.js +414 -0
  37. package/dist/preview.d.ts +25 -0
  38. package/dist/preview.js +296 -0
  39. package/dist/project.d.ts +15 -0
  40. package/dist/project.js +332 -0
  41. package/dist/schema.d.ts +22 -0
  42. package/dist/schema.js +155 -0
  43. package/dist/styles.d.ts +2 -0
  44. package/dist/styles.js +192 -0
  45. package/dist/types.d.ts +113 -0
  46. package/dist/types.js +0 -0
  47. package/dist/waveform.d.ts +16 -0
  48. package/dist/waveform.js +154 -0
  49. package/package.json +83 -4
package/dist/model.js ADDED
@@ -0,0 +1,560 @@
1
+ import {
2
+ asEditorError,
3
+ clone,
4
+ editorError,
5
+ equal,
6
+ getAudioEditorSchema,
7
+ knownFields,
8
+ merge,
9
+ validateAudioDocument
10
+ } from "./schema.js";
11
+ export * from "./memory.js";
12
+ export * from "./schema.js";
13
+ export * from "./types.js";
14
+ function segments(path) {
15
+ if (!path.startsWith("/") || path === "/")
16
+ throw editorError("invalid-document", "Use a JSON pointer to a document field.", path);
17
+ const parts = path.slice(1).split("/").map((part) => part.replace(/~1/g, "/").replace(/~0/g, "~"));
18
+ if (parts.some((part) => part === "__proto__" || part === "constructor" || part === "prototype"))
19
+ throw editorError("invalid-document", "Unsafe document field.", path);
20
+ return parts;
21
+ }
22
+ class AudioEditorSession {
23
+ documents;
24
+ assets;
25
+ preview;
26
+ current = {
27
+ dirty: false,
28
+ readOnly: false,
29
+ loading: false,
30
+ saving: false,
31
+ diagnostics: [],
32
+ canUndo: false,
33
+ canRedo: false,
34
+ historyEvicted: 0,
35
+ previewState: "idle"
36
+ };
37
+ baseline;
38
+ listeners = /* @__PURE__ */ new Set();
39
+ history = [];
40
+ future = [];
41
+ transaction;
42
+ limit;
43
+ byteLimit;
44
+ disposed = false;
45
+ generation = 0;
46
+ validationGeneration = 0;
47
+ validationAbort;
48
+ readAbort;
49
+ previewAbort;
50
+ unsubscribe;
51
+ unsubscribeAssets;
52
+ constructor(options) {
53
+ this.documents = options.documents;
54
+ this.assets = options.assets;
55
+ this.preview = options.preview;
56
+ this.limit = Math.max(1, options.historyLimit ?? 200);
57
+ this.byteLimit = Math.max(1, options.historyBytes ?? 16 * 1024 * 1024);
58
+ this.unsubscribe = this.documents.subscribe?.((ids) => {
59
+ if (this.disposed || this.current.saving) return;
60
+ if (this.current.document && ids.includes(this.current.document.id)) void this.remoteChanged();
61
+ else void this.refresh();
62
+ });
63
+ this.unsubscribeAssets = this.assets.subscribe?.(() => {
64
+ if (!this.disposed) void this.refresh();
65
+ });
66
+ }
67
+ get state() {
68
+ return {
69
+ ...this.current,
70
+ document: this.current.document && clone(this.current.document),
71
+ effective: this.current.effective && clone(this.current.effective),
72
+ conflict: this.current.conflict && clone(this.current.conflict),
73
+ diagnostics: [...this.current.diagnostics]
74
+ };
75
+ }
76
+ subscribe(listener) {
77
+ this.alive();
78
+ this.listeners.add(listener);
79
+ listener(this.state);
80
+ return () => {
81
+ this.listeners.delete(listener);
82
+ };
83
+ }
84
+ alive() {
85
+ if (this.disposed) throw editorError("disposed", "This editor session has been disposed.");
86
+ }
87
+ editable() {
88
+ this.alive();
89
+ if (this.current.readOnly) throw editorError("read-only", "This document is read-only.");
90
+ if (!this.current.document) throw editorError("invalid-document", "Open a document first.");
91
+ return this.current.document;
92
+ }
93
+ emit() {
94
+ if (this.disposed) return;
95
+ this.current.canUndo = this.history.length > 0;
96
+ this.current.canRedo = this.future.length > 0;
97
+ this.current.dirty = !!this.current.document && (!this.baseline?.revision || !equal(this.current.document.value, this.baseline.value));
98
+ for (const listener of this.listeners) listener(this.state);
99
+ }
100
+ adopt(document) {
101
+ const schema = getAudioEditorSchema(document.type);
102
+ this.baseline = clone(document);
103
+ this.current = {
104
+ ...this.current,
105
+ document: clone(document),
106
+ schema,
107
+ effective: void 0,
108
+ conflict: void 0,
109
+ loading: false,
110
+ diagnostics: [],
111
+ historyEvicted: 0,
112
+ readOnly: !this.documents.commit || !schema || (document.schemaVersion ?? 1) > schema.version
113
+ };
114
+ this.history = [];
115
+ this.future = [];
116
+ this.transaction = void 0;
117
+ }
118
+ async open(id) {
119
+ this.alive();
120
+ if (this.current.saving) throw editorError("conflict", "Wait for the current save to finish.");
121
+ if (this.current.dirty && this.current.document?.id !== id)
122
+ throw editorError(
123
+ "conflict",
124
+ "Save or discard the current draft before opening another document."
125
+ );
126
+ if (this.current.dirty && this.current.document?.id === id) return;
127
+ this.readAbort?.abort();
128
+ const controller = new AbortController();
129
+ this.readAbort = controller;
130
+ const generation = ++this.generation;
131
+ this.current.loading = true;
132
+ this.emit();
133
+ try {
134
+ const document = await this.documents.read(id, controller.signal);
135
+ if (this.disposed || generation !== this.generation) return;
136
+ if (document.id !== id)
137
+ throw editorError("invalid-document", "The host returned a different document ID.");
138
+ this.adopt(document);
139
+ await this.refresh();
140
+ } catch (error) {
141
+ if (this.disposed || generation !== this.generation) return;
142
+ this.current.loading = false;
143
+ this.current.diagnostics = [asEditorError(error)];
144
+ this.emit();
145
+ throw error;
146
+ }
147
+ }
148
+ async create(document) {
149
+ this.alive();
150
+ if (this.current.dirty || this.current.saving)
151
+ throw editorError("conflict", "Save or discard the current draft first.");
152
+ if (!this.documents.commit)
153
+ throw editorError("read-only", "The host does not support creating documents.");
154
+ ++this.generation;
155
+ this.readAbort?.abort();
156
+ this.adopt({ ...document, revision: "" });
157
+ await this.refresh();
158
+ }
159
+ async createNew(type, name, id = crypto.randomUUID()) {
160
+ const schema = getAudioEditorSchema(type);
161
+ if (!schema) throw editorError("unsupported-schema", `Unknown audio schema ${type}.`);
162
+ await this.create({
163
+ id,
164
+ name,
165
+ type,
166
+ revision: "",
167
+ schemaVersion: schema.version,
168
+ value: schema.serialize(schema.defaults())
169
+ });
170
+ }
171
+ async duplicate(name, id = crypto.randomUUID()) {
172
+ await this.saveAs(id, name);
173
+ }
174
+ edit(edit) {
175
+ const doc = this.editable();
176
+ const parts = segments(edit.path);
177
+ const before = clone(doc.value);
178
+ const next = clone(doc.value);
179
+ const top = parts[0];
180
+ if (parts.length > 1 && next[top] === void 0 && this.current.effective?.[top] !== void 0)
181
+ next[top] = clone(this.current.effective[top]);
182
+ let parent = next;
183
+ for (let i = 0; i < parts.length - 1; i++) {
184
+ let key2 = parts[i];
185
+ if (Array.isArray(parent)) {
186
+ const index = key2 === "-" ? parent.length : Number(key2);
187
+ if (!Number.isInteger(index) || index < 0 || index > parent.length)
188
+ throw editorError("invalid-document", "Invalid array index.", edit.path);
189
+ key2 = String(index);
190
+ }
191
+ let child = parent[key2];
192
+ if (!child || typeof child !== "object") {
193
+ child = {};
194
+ parent[key2] = child;
195
+ }
196
+ parent = child;
197
+ }
198
+ const key = parts.at(-1);
199
+ if (Array.isArray(parent)) {
200
+ const index = key === "-" ? parent.length : Number(key);
201
+ if (!Number.isInteger(index) || index < 0 || index > parent.length)
202
+ throw editorError("invalid-document", "Invalid array index.", edit.path);
203
+ if (edit.remove) parent.splice(index, 1);
204
+ else if (edit.value !== void 0) parent[index] = clone(edit.value);
205
+ } else if (edit.remove) delete parent[key];
206
+ else if (edit.value !== void 0) parent[key] = clone(edit.value);
207
+ doc.value = next;
208
+ if (!this.transaction) this.record(before, doc.value);
209
+ this.current.effective = merge(this.current.effective ?? {}, doc.value);
210
+ this.emit();
211
+ void this.refresh();
212
+ }
213
+ replace(value) {
214
+ const doc = this.editable();
215
+ const before = clone(doc.value);
216
+ doc.value = clone(value);
217
+ if (!this.transaction) this.record(before, doc.value);
218
+ this.emit();
219
+ void this.refresh();
220
+ }
221
+ beginTransaction(_label) {
222
+ const doc = this.editable();
223
+ if (this.transaction)
224
+ throw editorError("conflict", "Finish the current edit transaction first.");
225
+ this.transaction = clone(doc.value);
226
+ }
227
+ endTransaction() {
228
+ const doc = this.editable();
229
+ if (!this.transaction) return;
230
+ this.record(this.transaction, doc.value);
231
+ this.transaction = void 0;
232
+ this.emit();
233
+ }
234
+ commitTransaction() {
235
+ this.endTransaction();
236
+ }
237
+ cancelTransaction() {
238
+ const doc = this.editable();
239
+ if (!this.transaction) return;
240
+ doc.value = this.transaction;
241
+ this.transaction = void 0;
242
+ this.emit();
243
+ void this.refresh();
244
+ }
245
+ record(before, after) {
246
+ if (equal(before, after)) return;
247
+ const entry = {
248
+ before: clone(before),
249
+ after: clone(after),
250
+ bytes: 2 * (JSON.stringify(before).length + JSON.stringify(after).length)
251
+ };
252
+ this.history.push(entry);
253
+ this.future = [];
254
+ let bytes = this.history.reduce((total, item) => total + item.bytes, 0);
255
+ while (this.history.length > this.limit || bytes > this.byteLimit) {
256
+ bytes -= this.history.shift().bytes;
257
+ this.current.historyEvicted++;
258
+ }
259
+ }
260
+ undo() {
261
+ const doc = this.editable();
262
+ if (this.transaction) this.cancelTransaction();
263
+ const entry = this.history.pop();
264
+ if (!entry) return;
265
+ this.future.push(entry);
266
+ doc.value = clone(entry.before);
267
+ this.emit();
268
+ void this.refresh();
269
+ }
270
+ redo() {
271
+ const doc = this.editable();
272
+ const entry = this.future.pop();
273
+ if (!entry) return;
274
+ this.history.push(entry);
275
+ doc.value = clone(entry.after);
276
+ this.emit();
277
+ void this.refresh();
278
+ }
279
+ async refresh() {
280
+ const document = this.current.document;
281
+ if (!document || this.disposed) {
282
+ this.emit();
283
+ return;
284
+ }
285
+ const sequence = ++this.validationGeneration;
286
+ this.validationAbort?.abort();
287
+ this.validationAbort = new AbortController();
288
+ const result = await validateAudioDocument(
289
+ clone(document),
290
+ this.documents,
291
+ this.assets,
292
+ this.validationAbort.signal
293
+ );
294
+ if (this.disposed || sequence !== this.validationGeneration || document.id !== this.current.document?.id)
295
+ return;
296
+ this.current.diagnostics = result.errors;
297
+ this.current.effective = result.effective?.value ?? clone(document.value);
298
+ this.emit();
299
+ }
300
+ async validate() {
301
+ this.alive();
302
+ await this.refresh();
303
+ return [...this.current.diagnostics];
304
+ }
305
+ async save() {
306
+ const doc = this.editable();
307
+ if (this.current.saving) throw editorError("conflict", "A save is already in progress.");
308
+ if (this.transaction) this.endTransaction();
309
+ if (!this.current.dirty) return clone(doc);
310
+ if (this.current.conflict)
311
+ throw editorError("conflict", "Resolve the external edit before saving.");
312
+ const snapshot = clone(doc);
313
+ this.current.saving = true;
314
+ this.emit();
315
+ try {
316
+ const result = await validateAudioDocument(snapshot, this.documents, this.assets);
317
+ if (result.errors.length) throw result.errors[0];
318
+ const saved = await this.documents.commit([
319
+ { document: snapshot, expectedRevision: this.baseline?.revision || null }
320
+ ]);
321
+ const confirmed = saved.find((item) => item.id === doc.id);
322
+ if (!confirmed?.revision)
323
+ throw editorError("invalid-document", "The host did not acknowledge the saved revision.");
324
+ if (this.disposed) return clone(confirmed);
325
+ this.baseline = clone(confirmed);
326
+ doc.revision = confirmed.revision;
327
+ doc.sourceText = confirmed.sourceText;
328
+ if (equal(doc.value, snapshot.value)) doc.value = clone(confirmed.value);
329
+ return clone(confirmed);
330
+ } catch (error) {
331
+ if (!this.disposed) this.current.diagnostics = [asEditorError(error)];
332
+ throw error;
333
+ } finally {
334
+ this.current.saving = false;
335
+ this.emit();
336
+ }
337
+ }
338
+ async saveAs(id, name) {
339
+ const current = this.editable();
340
+ if (this.current.saving) throw editorError("conflict", "Wait for the pending save.");
341
+ if (id === current.id)
342
+ throw editorError("conflict", "Save as needs a different host document ID.");
343
+ const document = { ...clone(current), id, name, revision: "" };
344
+ delete document.sourceText;
345
+ this.current.saving = true;
346
+ this.emit();
347
+ try {
348
+ const result = await validateAudioDocument(document, this.documents, this.assets);
349
+ if (result.errors.length) throw result.errors[0];
350
+ const saved = await this.documents.commit([{ document, expectedRevision: null }]);
351
+ this.alive();
352
+ const created = saved.find((item) => item.id === id);
353
+ if (!created?.revision)
354
+ throw editorError("invalid-document", "The host did not acknowledge the created document.");
355
+ const pending = equal(current.value, document.value) ? void 0 : clone(current.value);
356
+ this.adopt(created);
357
+ if (pending) this.current.document.value = pending;
358
+ await this.refresh();
359
+ } finally {
360
+ this.current.saving = false;
361
+ this.emit();
362
+ }
363
+ }
364
+ async reload() {
365
+ this.alive();
366
+ if (this.current.saving) throw editorError("conflict", "Wait for the pending save.");
367
+ const id = this.current.document?.id;
368
+ if (!id) return;
369
+ const doc = await this.documents.read(id);
370
+ this.alive();
371
+ if (this.current.document?.id !== id) return;
372
+ this.adopt(doc);
373
+ await this.refresh();
374
+ }
375
+ async remoteChanged() {
376
+ const id = this.current.document?.id;
377
+ if (!id) return;
378
+ try {
379
+ const remote = await this.documents.read(id);
380
+ if (this.disposed || this.current.document?.id !== id || remote.revision === this.baseline?.revision)
381
+ return;
382
+ if (this.current.dirty) this.current.conflict = clone(remote);
383
+ else this.adopt(remote);
384
+ await this.refresh();
385
+ } catch (error) {
386
+ if (!this.disposed) {
387
+ this.current.diagnostics = [asEditorError(error)];
388
+ this.emit();
389
+ }
390
+ }
391
+ }
392
+ /** Batch edits have an explicit adapter capability and one compare-and-swap boundary. */
393
+ async commitDocuments(changes) {
394
+ this.alive();
395
+ if (!this.documents.commit || changes.length > 1 && !this.documents.atomic)
396
+ throw editorError(
397
+ "unsupported-capability",
398
+ "The host cannot commit this atomic document batch."
399
+ );
400
+ const overlay = new Map(changes.map((change) => [change.document.id, change.document]));
401
+ const documents = {
402
+ ...this.documents,
403
+ read: async (id, signal) => clone(overlay.get(id) ?? await this.documents.read(id, signal))
404
+ };
405
+ for (const change of changes) {
406
+ const validation = await validateAudioDocument(change.document, documents, this.assets);
407
+ if (validation.errors.length) throw validation.errors[0];
408
+ }
409
+ return this.documents.commit(changes);
410
+ }
411
+ /** Explicit dependent IDs avoid renaming an unrelated parameter with the same display name. */
412
+ async renameParameter(from, to, dependentIds = []) {
413
+ const current = this.editable();
414
+ if (this.current.dirty || this.current.saving)
415
+ throw editorError("conflict", "Save the current draft before a parameter rename.");
416
+ if (!to || from === to)
417
+ throw editorError("invalid-document", "Choose a different nonempty parameter name.");
418
+ const global = current.type === "audio/AudioParameters";
419
+ if (dependentIds.length && !global)
420
+ throw editorError(
421
+ "invalid-document",
422
+ "Local parameter renames cannot change other documents."
423
+ );
424
+ const ids = [.../* @__PURE__ */ new Set([current.id, ...dependentIds])];
425
+ if (ids.length > 1 && !this.documents.atomic)
426
+ throw editorError(
427
+ "unsupported-capability",
428
+ "This host does not support atomic reference renames."
429
+ );
430
+ const changes = [];
431
+ for (const id of ids) {
432
+ const source = await this.documents.read(id);
433
+ const next = clone(source);
434
+ if (id !== current.id) {
435
+ const globals = next.value.globals;
436
+ if ((globals?.guid ?? globals?.path) !== current.id)
437
+ throw editorError(
438
+ "invalid-document",
439
+ `Document ${id} does not reference this parameter bank.`
440
+ );
441
+ const locals = next.value.parameters;
442
+ if (Array.isArray(locals) && locals.some(
443
+ (p) => typeof p === "object" && p !== null && !Array.isArray(p) && p.name === from
444
+ ))
445
+ throw editorError(
446
+ "conflict",
447
+ `Document ${id} shadows this global parameter with a local name.`
448
+ );
449
+ } else {
450
+ const definitions = next.value.parameters;
451
+ if (!Array.isArray(definitions))
452
+ throw editorError("invalid-document", "This document has no authored parameters.");
453
+ let found = false;
454
+ for (const entry of definitions) {
455
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
456
+ if (entry.name === to)
457
+ throw editorError("conflict", "That parameter name already exists.");
458
+ if (entry.name === from) {
459
+ entry.name = to;
460
+ found = true;
461
+ }
462
+ }
463
+ if (!found)
464
+ throw editorError(
465
+ "invalid-document",
466
+ "Parameter is inherited or does not exist; rename it in its defining document."
467
+ );
468
+ }
469
+ const update = (value, known) => {
470
+ if (Array.isArray(value) && Array.isArray(known)) {
471
+ value.forEach((item, index) => {
472
+ update(item, known[index] ?? null);
473
+ });
474
+ return;
475
+ }
476
+ if (!value || typeof value !== "object" || Array.isArray(value) || !known || typeof known !== "object")
477
+ return;
478
+ for (const key of Object.keys(known)) {
479
+ if ((key === "parameter" || key === "selectParameter") && value[key] === from)
480
+ value[key] = to;
481
+ else update(value[key], known[key]);
482
+ }
483
+ };
484
+ const schema = getAudioEditorSchema(next.type);
485
+ if (!schema)
486
+ throw editorError("unsupported-schema", `Unsupported document schema ${next.type}.`);
487
+ update(next.value, knownFields(schema.fields, next.value));
488
+ changes.push({ document: next, expectedRevision: source.revision });
489
+ }
490
+ await this.commitDocuments(changes);
491
+ await this.reload();
492
+ }
493
+ async play() {
494
+ this.alive();
495
+ if (!this.preview)
496
+ throw editorError("unsupported-capability", "This host does not provide audio preview.");
497
+ if (!this.current.document)
498
+ throw editorError("invalid-document", "Open a document to audition.");
499
+ const document = clone(this.current.document);
500
+ this.previewAbort?.abort();
501
+ const controller = new AbortController();
502
+ this.previewAbort = controller;
503
+ this.current.previewState = "preparing";
504
+ this.emit();
505
+ try {
506
+ const result = await this.preview.play(document, controller.signal);
507
+ if (!this.disposed && !controller.signal.aborted) {
508
+ this.current.previewState = "playing";
509
+ this.current.previewResult = result;
510
+ this.current.previewDocumentId = document.id;
511
+ this.emit();
512
+ }
513
+ return result;
514
+ } catch (error) {
515
+ if (!this.disposed && !controller.signal.aborted) {
516
+ this.current.previewState = "failed";
517
+ this.current.diagnostics = [asEditorError(error)];
518
+ this.emit();
519
+ }
520
+ throw error;
521
+ }
522
+ }
523
+ async stop() {
524
+ this.previewAbort?.abort();
525
+ await this.preview?.stop();
526
+ this.current.previewState = "idle";
527
+ this.current.previewResult = void 0;
528
+ this.current.previewDocumentId = void 0;
529
+ this.emit();
530
+ }
531
+ async dispose() {
532
+ if (this.disposed) return;
533
+ this.disposed = true;
534
+ ++this.generation;
535
+ ++this.validationGeneration;
536
+ this.readAbort?.abort();
537
+ this.validationAbort?.abort();
538
+ this.previewAbort?.abort();
539
+ this.unsubscribe?.();
540
+ this.unsubscribeAssets?.();
541
+ this.listeners.clear();
542
+ this.history = [];
543
+ this.future = [];
544
+ this.transaction = void 0;
545
+ this.baseline = void 0;
546
+ this.current.document = void 0;
547
+ this.current.effective = void 0;
548
+ this.current.conflict = void 0;
549
+ this.current.previewResult = void 0;
550
+ this.current.previewDocumentId = void 0;
551
+ await this.preview?.dispose();
552
+ }
553
+ }
554
+ function createAudioEditorSession(options) {
555
+ return new AudioEditorSession(options);
556
+ }
557
+ export {
558
+ AudioEditorSession,
559
+ createAudioEditorSession
560
+ };
@@ -0,0 +1,33 @@
1
+ import { type ComponentDef, type JsonObject, type JsonValue } from '@aethervtt/shard-core';
2
+ import type { AudioEditorSession } from '../model.js';
3
+ export type AudioEditorPanel = 'library' | 'event' | 'mixer' | 'music' | 'parameters' | 'diagnostics';
4
+ export interface AudioEditorMountOptions {
5
+ session: AudioEditorSession;
6
+ theme?: 'dark' | 'light';
7
+ layout?: 'full' | 'compact';
8
+ /** A host-served copy of audioEditorStyles, for CSPs forbidding inline style elements. */
9
+ stylesheetUrl?: string;
10
+ strings?: Readonly<Record<string, string>>;
11
+ scene?: AudioEditorSceneSelection;
12
+ }
13
+ /** Optional scene component selection. The host owns its history and persistence. */
14
+ export interface AudioEditorSceneSelection {
15
+ schema: ComponentDef;
16
+ value: JsonObject;
17
+ readOnly?: boolean;
18
+ edit(change: {
19
+ path: string;
20
+ value?: JsonValue;
21
+ remove?: boolean;
22
+ }): void;
23
+ }
24
+ export interface AudioEditorView {
25
+ readonly element: HTMLElement;
26
+ readonly root: ShadowRoot;
27
+ setTheme(theme: 'dark' | 'light'): void;
28
+ /** Rerender after the host updates its optional scene selection. */
29
+ refresh(): void;
30
+ dispose(): void;
31
+ }
32
+ /** All panels share this command-driven renderer; mount never starts audio. */
33
+ export declare function mountPanel(container: HTMLElement, options: AudioEditorMountOptions, fixedPanel?: AudioEditorPanel): AudioEditorView;