@sciflow/editor-start 0.0.1-beta

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.
@@ -0,0 +1,56 @@
1
+ import {
2
+ citationFeature,
3
+ figureFeature,
4
+ headingFeature,
5
+ markFormattingFeature,
6
+ } from '../bundle/sciflow-editor.js';
7
+
8
+ const editor = document.querySelector('#demo-editor');
9
+ const statusEl = document.querySelector('#demo-status');
10
+ const docOutput = document.querySelector('#doc-json');
11
+
12
+ if (!editor) {
13
+ throw new Error('Unable to find <sciflow-editor id="demo-editor"> in demo.html');
14
+ }
15
+
16
+ const DEMO_FEATURES = [figureFeature, citationFeature, headingFeature, markFormattingFeature];
17
+
18
+ const renderDoc = (doc, label = 'editor-change') => {
19
+ try {
20
+ docOutput.textContent = JSON.stringify(doc, null, 2);
21
+ } catch {
22
+ docOutput.textContent = `${label} payload could not be stringified. See console for details.`;
23
+ }
24
+ };
25
+
26
+ const updateStatus = (message) => {
27
+ if (statusEl) {
28
+ statusEl.textContent = message;
29
+ }
30
+ };
31
+
32
+ const applyFeatures = async () => {
33
+ updateStatus('Configuring features…');
34
+ if (typeof editor.configureFeatures === 'function') {
35
+ await editor.configureFeatures(DEMO_FEATURES);
36
+ } else {
37
+ editor.features = [...DEMO_FEATURES];
38
+ }
39
+ updateStatus('Editor ready – start typing!');
40
+ };
41
+
42
+ editor.addEventListener('editor-ready', () => {
43
+ updateStatus('Editor ready – start typing!');
44
+ });
45
+
46
+ editor.addEventListener('editor-change', (event) => {
47
+ if (!event.detail?.doc) {
48
+ return;
49
+ }
50
+ renderDoc(event.detail.doc);
51
+ });
52
+
53
+ applyFeatures().catch((error) => {
54
+ console.error('[demo] Failed to configure features', error);
55
+ updateStatus('Failed to configure features – check console output.');
56
+ });
@@ -0,0 +1,169 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>SciFlow Editor – Minimal Demo</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <link
8
+ rel="stylesheet"
9
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,400,0..1,0"
10
+ />
11
+ <style>
12
+ :root {
13
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
14
+ color: #0f172a;
15
+ background: #f8fafc;
16
+ }
17
+
18
+ * {
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ body {
23
+ margin: 0;
24
+ min-height: 100vh;
25
+ background: radial-gradient(circle at top, #e0f2fe 0%, #f8fafc 40%, #f8fafc 100%);
26
+ color: inherit;
27
+ }
28
+
29
+ main {
30
+ max-width: 1200px;
31
+ margin: 0 auto;
32
+ padding: 32px 24px 64px;
33
+ display: flex;
34
+ flex-direction: column;
35
+ gap: 24px;
36
+ }
37
+
38
+ header h1 {
39
+ margin: 0 0 8px;
40
+ font-size: clamp(1.75rem, 3vw, 2.5rem);
41
+ }
42
+
43
+ header p {
44
+ margin: 0;
45
+ max-width: 720px;
46
+ line-height: 1.5;
47
+ }
48
+
49
+ code {
50
+ padding: 2px 6px;
51
+ border-radius: 4px;
52
+ background: rgba(15, 23, 42, 0.05);
53
+ font-size: 0.9em;
54
+ }
55
+
56
+ .demo-layout {
57
+ display: grid;
58
+ gap: 20px;
59
+ grid-template-columns: minmax(0, 2fr) minmax(240px, 1fr);
60
+ }
61
+
62
+ .panel {
63
+ background: white;
64
+ border-radius: 16px;
65
+ padding: 20px;
66
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 0.1);
67
+ }
68
+
69
+ .editor-panel {
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: 16px;
73
+ }
74
+
75
+ sciflow-editor {
76
+ display: block;
77
+ min-height: 420px;
78
+ border: 1px solid rgba(15, 23, 42, 0.08);
79
+ border-radius: 16px;
80
+ overflow: hidden;
81
+ background: white;
82
+ }
83
+
84
+ .toolbar-row {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 12px;
88
+ flex-wrap: wrap;
89
+ }
90
+
91
+ .status-pill {
92
+ font-size: 0.9rem;
93
+ padding: 6px 12px;
94
+ border-radius: 999px;
95
+ background: rgba(37, 99, 235, 0.12);
96
+ color: #1d4ed8;
97
+ }
98
+
99
+ aside h2 {
100
+ margin-top: 0;
101
+ }
102
+
103
+ pre {
104
+ margin: 12px 0 0;
105
+ padding: 12px;
106
+ background: #0f172a;
107
+ color: #e2e8f0;
108
+ border-radius: 12px;
109
+ min-height: 200px;
110
+ overflow: auto;
111
+ font-size: 0.85rem;
112
+ }
113
+
114
+ footer {
115
+ font-size: 0.95rem;
116
+ color: rgba(15, 23, 42, 0.75);
117
+ line-height: 1.6;
118
+ }
119
+
120
+ @media (max-width: 960px) {
121
+ .demo-layout {
122
+ grid-template-columns: 1fr;
123
+ }
124
+
125
+ aside {
126
+ order: -1;
127
+ }
128
+ }
129
+ </style>
130
+ </head>
131
+ <body>
132
+ <main>
133
+ <header>
134
+ <h1>SciFlow Editor quick start</h1>
135
+ <p>
136
+ This file ships with the <code>@sciflow/editor-start</code> package so you can validate the bundled custom
137
+ elements without wiring a framework. Run <code>npx nx bundle @sciflow/editor-start</code> (or use the published
138
+ tarball), open this HTML page from <code>dist/demo</code>, and start typing.
139
+ </p>
140
+ </header>
141
+
142
+ <section class="demo-layout">
143
+ <div class="panel editor-panel">
144
+ <div class="toolbar-row">
145
+ <sciflow-formatbar for="demo-editor"></sciflow-formatbar>
146
+ <span class="status-pill" id="demo-status">Loading editor bundle…</span>
147
+ </div>
148
+ <sciflow-editor id="demo-editor" aria-label="SciFlow editor demo"></sciflow-editor>
149
+ </div>
150
+
151
+ <aside class="panel">
152
+ <h2>Live document JSON</h2>
153
+ <p>
154
+ The script mirrors the payload from <code>editor-change</code> so you can inspect the serialized document and
155
+ reuse the event contract in your integration.
156
+ </p>
157
+ <pre id="doc-json" aria-live="polite">Waiting for the first editor-change event…</pre>
158
+ </aside>
159
+ </section>
160
+
161
+ <footer>
162
+ <strong>Tip:</strong> Inspect the <code>demo.js</code> module next to this file to see how features are configured
163
+ and how to listen for <code>editor-ready</code> / <code>editor-change</code> events using plain web components.
164
+ </footer>
165
+ </main>
166
+
167
+ <script type="module" src="./demo.js"></script>
168
+ </body>
169
+ </html>
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Entry point for consumers of `@sciflow/editor-start`.
3
+ *
4
+ * Exports the web components implemented in this package so they can be
5
+ * registered or extended by host applications. This barrel file mirrors the
6
+ * bundling strategy used for the demo so integrators have a single import path.
7
+ */
8
+ export * from './lib/editor-element.js';
9
+ export * from './lib/format-bar.js';
10
+ export * from './lib/outline.js';
11
+ export * from './lib/reference-list.js';
12
+ export * from './lib/selection-editor.js';
13
+ export * from './lib/theme.js';
14
+ export { defaultCitationSourceAdapter, getCitationSourceAdapter, setCitationSourceAdapter, type CitationItem, type CitationSourceEditorAdapter, type CitationSourceEditorContext, } from './lib/citation-source-adapter.js';
15
+ export { SourceField } from '@sciflow/editor-core';
16
+ export { citationFeature, createFigureFeature, listFeature, blockquoteFeature, copyPasteFeature, installMockImageServer, configureImageUploadHandlers, requestImageFile, uploadImageFile, promptForFigureOptions, figureFeature, headingFeature, markFormattingFeature, tableFeature, } from '@sciflow/editor-core';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,wBAAwB,EACxB,KAAK,YAAY,EACjB,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,GACjC,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,4BAA4B,EAC5B,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,YAAY,GACb,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Entry point for consumers of `@sciflow/editor-start`.
3
+ *
4
+ * Exports the web components implemented in this package so they can be
5
+ * registered or extended by host applications. This barrel file mirrors the
6
+ * bundling strategy used for the demo so integrators have a single import path.
7
+ */
8
+ export * from './lib/editor-element.js';
9
+ export * from './lib/format-bar.js';
10
+ export * from './lib/outline.js';
11
+ export * from './lib/reference-list.js';
12
+ export * from './lib/selection-editor.js';
13
+ export * from './lib/theme.js';
14
+ export { defaultCitationSourceAdapter, getCitationSourceAdapter, setCitationSourceAdapter, } from './lib/citation-source-adapter.js';
15
+ // Convenience re-export: allow downstream bundles to pull SourceField from here.
16
+ export { SourceField } from '@sciflow/editor-core';
17
+ export { citationFeature, createFigureFeature, listFeature, blockquoteFeature, copyPasteFeature, installMockImageServer, configureImageUploadHandlers, requestImageFile, uploadImageFile, promptForFigureOptions, figureFeature, headingFeature, markFormattingFeature, tableFeature, } from '@sciflow/editor-core';
@@ -0,0 +1,25 @@
1
+ import type { EditorView } from 'prosemirror-view';
2
+ type CitationNode = {
3
+ node: any;
4
+ pos: number;
5
+ };
6
+ /**
7
+ * Find the citation node at or above a document position.
8
+ */
9
+ export declare const findCitationAtPosition: (view: EditorView, pos: number) => CitationNode | null;
10
+ /**
11
+ * Merge a new citation id into an existing citation node.
12
+ * Replaces the node with updated attrs/content, avoiding nested citations.
13
+ */
14
+ export declare const mergeCitationWithId: (view: EditorView, target: CitationNode, newId: string) => boolean;
15
+ /**
16
+ * Append a citation id to an encoded source field string, returning the encoded value and items.
17
+ */
18
+ export declare const appendCitationItem: (encoded: string | null, newId: string) => {
19
+ encoded: string | null;
20
+ items: Array<{
21
+ id: string;
22
+ }>;
23
+ } | null;
24
+ export {};
25
+ //# sourceMappingURL=citation-drop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citation-drop.d.ts","sourceRoot":"","sources":["../../src/lib/citation-drop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM,UAAU,EAAE,KAAK,MAAM,KAAG,YAAY,GAAG,IAYrF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,UAAU,EAChB,QAAQ,YAAY,EACpB,OAAO,MAAM,KACZ,OA0BF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,MAAM,GAAG,IAAI,EACtB,OAAO,MAAM,KACZ;IAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,IAS7D,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { SourceField } from '@sciflow/editor-core';
2
+ /**
3
+ * Find the citation node at or above a document position.
4
+ */
5
+ export const findCitationAtPosition = (view, pos) => {
6
+ if (!view || typeof pos !== 'number') {
7
+ return null;
8
+ }
9
+ const $pos = view.state.doc.resolve(pos);
10
+ for (let depth = $pos.depth; depth >= 0; depth--) {
11
+ const node = $pos.node(depth);
12
+ if (node.type.name === 'citation') {
13
+ return { node, pos: $pos.before(depth) };
14
+ }
15
+ }
16
+ return null;
17
+ };
18
+ /**
19
+ * Merge a new citation id into an existing citation node.
20
+ * Replaces the node with updated attrs/content, avoiding nested citations.
21
+ */
22
+ export const mergeCitationWithId = (view, target, newId) => {
23
+ if (!view || !target || !newId) {
24
+ return false;
25
+ }
26
+ const result = appendCitationItem(target.node.attrs?.source ?? null, newId);
27
+ if (!result?.encoded) {
28
+ return false;
29
+ }
30
+ const citationType = view.state.schema.nodes.citation;
31
+ if (!citationType) {
32
+ return false;
33
+ }
34
+ const displayText = `[${result.items.map((item) => item?.id).filter(Boolean).join('; ')}]`;
35
+ const textNode = view.state.schema.text(displayText);
36
+ const newNode = citationType.create({ ...target.node.attrs, source: result.encoded }, textNode ? [textNode] : undefined, target.node.marks);
37
+ const tr = view.state.tr.replaceWith(target.pos, target.pos + target.node.nodeSize, newNode);
38
+ view.dispatch(tr);
39
+ return true;
40
+ };
41
+ /**
42
+ * Append a citation id to an encoded source field string, returning the encoded value and items.
43
+ */
44
+ export const appendCitationItem = (encoded, newId) => {
45
+ if (!newId) {
46
+ return null;
47
+ }
48
+ const items = SourceField.fromString(encoded ?? null) ?? [];
49
+ if (!items.find((item) => item?.id === newId)) {
50
+ items.push({ id: newId });
51
+ }
52
+ return { encoded: SourceField.toString(items), items };
53
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @module citation-source-adapter
3
+ *
4
+ * Pluggable UI for editing the citation node `source` attribute.
5
+ * The editor applies attribute changes via transactions; this adapter
6
+ * handles schema-aware rendering and serialization.
7
+ */
8
+ import type { CitationItem } from '@sciflow/editor-core';
9
+ export type { CitationItem };
10
+ /**
11
+ * Context passed to the adapter when rendering or updating.
12
+ */
13
+ export interface CitationSourceEditorContext {
14
+ /** Apply updated source string to the node via command API. */
15
+ applySource: (encoded: string | null) => boolean;
16
+ }
17
+ /**
18
+ * Adapter interface for rendering and editing citation source (CSL citation items).
19
+ * Apps can register a custom adapter to provide JSON-schema-driven or other UIs.
20
+ */
21
+ export interface CitationSourceEditorAdapter {
22
+ /**
23
+ * Render the editor UI into the given container.
24
+ * @param container - DOM element to render into
25
+ * @param value - Current citation items (decoded from source attribute)
26
+ * @param context - Callbacks to apply changes
27
+ */
28
+ render(container: HTMLElement, value: CitationItem[], context: CitationSourceEditorContext): void;
29
+ /**
30
+ * Update the displayed value when the selection or node changes externally.
31
+ * @param container - Same container passed to render
32
+ * @param value - New citation items
33
+ * @param context - Same context passed to render (for re-rendering)
34
+ */
35
+ update?(container: HTMLElement, value: CitationItem[], context: CitationSourceEditorContext): void;
36
+ /**
37
+ * Clean up resources when the editor is torn down.
38
+ * @param container - Same container passed to render
39
+ */
40
+ destroy?(container: HTMLElement): void;
41
+ }
42
+ /**
43
+ * Default adapter: form fields for id, label, locator, prefix, suffix,
44
+ * suppress-author, and author-only per citation item.
45
+ */
46
+ export declare const defaultCitationSourceAdapter: CitationSourceEditorAdapter;
47
+ /**
48
+ * Register a custom citation source editor adapter.
49
+ * Pass null to restore the default.
50
+ */
51
+ export declare function setCitationSourceAdapter(adapter: CitationSourceEditorAdapter | null): void;
52
+ /**
53
+ * Get the current adapter (custom or default).
54
+ */
55
+ export declare function getCitationSourceAdapter(): CitationSourceEditorAdapter;
56
+ //# sourceMappingURL=citation-source-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citation-source-adapter.d.ts","sourceRoot":"","sources":["../../src/lib/citation-source-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,+DAA+D;IAC/D,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC;CAClD;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;;;OAKG;IACH,MAAM,CACJ,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,EAAE,2BAA2B,GACnC,IAAI,CAAC;IAER;;;;;OAKG;IACH,MAAM,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAEnG;;;OAGG;IACH,OAAO,CAAC,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CACxC;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,2BA0G1C,CAAC;AAKF;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,2BAA2B,GAAG,IAAI,GAAG,IAAI,CAE1F;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,2BAA2B,CAEtE"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @module citation-source-adapter
3
+ *
4
+ * Pluggable UI for editing the citation node `source` attribute.
5
+ * The editor applies attribute changes via transactions; this adapter
6
+ * handles schema-aware rendering and serialization.
7
+ */
8
+ import { SourceField } from '@sciflow/editor-core';
9
+ /**
10
+ * Default adapter: form fields for id, label, locator, prefix, suffix,
11
+ * suppress-author, and author-only per citation item.
12
+ */
13
+ export const defaultCitationSourceAdapter = {
14
+ render(container, value, context) {
15
+ container.innerHTML = '';
16
+ const items = value.length > 0 ? [...value] : [{ id: '' }];
17
+ const apply = () => {
18
+ const normalized = items
19
+ .map((item) => {
20
+ const id = typeof item?.id === 'string' ? item.id.trim() : '';
21
+ if (!id)
22
+ return null;
23
+ return { ...item, id };
24
+ })
25
+ .filter((item) => item !== null);
26
+ const encoded = SourceField.toString(normalized);
27
+ context.applySource(encoded);
28
+ };
29
+ items.forEach((item, index) => {
30
+ const section = document.createElement('div');
31
+ section.className = 'citation-source-item';
32
+ section.setAttribute('data-citation-index', String(index));
33
+ const fields = [
34
+ { key: 'id', label: 'ID', type: 'text', required: true },
35
+ { key: 'label', label: 'Label', type: 'text' },
36
+ { key: 'locator', label: 'Locator', type: 'text' },
37
+ { key: 'prefix', label: 'Prefix', type: 'text' },
38
+ { key: 'suffix', label: 'Suffix', type: 'text' },
39
+ { key: 'suppress-author', label: 'Suppress author', type: 'checkbox' },
40
+ { key: 'author-only', label: 'Author only', type: 'checkbox' },
41
+ ];
42
+ fields.forEach(({ key, label, type }) => {
43
+ const fieldDiv = document.createElement('div');
44
+ fieldDiv.className = 'citation-source-field';
45
+ fieldDiv.setAttribute('data-field', key);
46
+ const idAttr = `citation-${index}-${key}`;
47
+ const labelEl = document.createElement('label');
48
+ labelEl.htmlFor = idAttr;
49
+ labelEl.className = 'selection-editor-attr-label';
50
+ labelEl.textContent = `${label}:`;
51
+ fieldDiv.appendChild(labelEl);
52
+ if (type === 'checkbox') {
53
+ const input = document.createElement('input');
54
+ input.type = 'checkbox';
55
+ input.id = idAttr;
56
+ input.className = 'selection-editor-input';
57
+ const val = item[key];
58
+ input.checked = val === 1 || val === true;
59
+ input.addEventListener('change', () => {
60
+ items[index][key] = input.checked ? 1 : undefined;
61
+ apply();
62
+ });
63
+ fieldDiv.appendChild(input);
64
+ }
65
+ else {
66
+ const input = document.createElement('input');
67
+ input.type = 'text';
68
+ input.id = idAttr;
69
+ input.className = 'selection-editor-input';
70
+ input.placeholder = 'No value';
71
+ const val = item[key];
72
+ input.value = typeof val === 'string' ? val : val != null ? String(val) : '';
73
+ input.addEventListener('change', () => {
74
+ const v = input.value.trim();
75
+ items[index][key] = v || undefined;
76
+ apply();
77
+ });
78
+ fieldDiv.appendChild(input);
79
+ }
80
+ section.appendChild(fieldDiv);
81
+ });
82
+ if (items.length > 1) {
83
+ const removeBtn = document.createElement('button');
84
+ removeBtn.type = 'button';
85
+ removeBtn.className = 'citation-source-remove';
86
+ removeBtn.textContent = 'Remove';
87
+ removeBtn.addEventListener('click', () => {
88
+ items.splice(index, 1);
89
+ defaultCitationSourceAdapter.render(container, items, context);
90
+ });
91
+ section.appendChild(removeBtn);
92
+ }
93
+ container.appendChild(section);
94
+ });
95
+ const addBtn = document.createElement('button');
96
+ addBtn.type = 'button';
97
+ addBtn.className = 'citation-source-add';
98
+ addBtn.textContent = 'Add citation item';
99
+ addBtn.addEventListener('click', () => {
100
+ items.push({ id: '' });
101
+ defaultCitationSourceAdapter.render(container, items, context);
102
+ });
103
+ container.appendChild(addBtn);
104
+ },
105
+ update(container, value, context) {
106
+ defaultCitationSourceAdapter.render(container, value, context);
107
+ },
108
+ destroy(container) {
109
+ container.innerHTML = '';
110
+ },
111
+ };
112
+ /** Registry for custom adapters. When set, overrides the default. */
113
+ let customAdapter = null;
114
+ /**
115
+ * Register a custom citation source editor adapter.
116
+ * Pass null to restore the default.
117
+ */
118
+ export function setCitationSourceAdapter(adapter) {
119
+ customAdapter = adapter;
120
+ }
121
+ /**
122
+ * Get the current adapter (custom or default).
123
+ */
124
+ export function getCitationSourceAdapter() {
125
+ return customAdapter ?? defaultCitationSourceAdapter;
126
+ }