@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.
- package/README.md +130 -0
- package/dist/bundle/sciflow-editor.css +1 -0
- package/dist/bundle/sciflow-editor.js +18739 -0
- package/dist/demo/demo.js +56 -0
- package/dist/demo/index.html +169 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/lib/citation-drop.d.ts +25 -0
- package/dist/lib/citation-drop.d.ts.map +1 -0
- package/dist/lib/citation-drop.js +53 -0
- package/dist/lib/citation-source-adapter.d.ts +56 -0
- package/dist/lib/citation-source-adapter.d.ts.map +1 -0
- package/dist/lib/citation-source-adapter.js +126 -0
- package/dist/lib/editor-element.d.ts +256 -0
- package/dist/lib/editor-element.d.ts.map +1 -0
- package/dist/lib/editor-element.js +681 -0
- package/dist/lib/format-bar.d.ts +98 -0
- package/dist/lib/format-bar.d.ts.map +1 -0
- package/dist/lib/format-bar.js +401 -0
- package/dist/lib/outline.d.ts +94 -0
- package/dist/lib/outline.d.ts.map +1 -0
- package/dist/lib/outline.js +491 -0
- package/dist/lib/reference-list.d.ts +58 -0
- package/dist/lib/reference-list.d.ts.map +1 -0
- package/dist/lib/reference-list.js +160 -0
- package/dist/lib/selection-editor.d.ts +94 -0
- package/dist/lib/selection-editor.d.ts.map +1 -0
- package/dist/lib/selection-editor.js +467 -0
- package/dist/lib/theme.d.ts +8 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +72 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module selection-editor
|
|
3
|
+
*
|
|
4
|
+
* Developer overview:
|
|
5
|
+
* --------------------
|
|
6
|
+
* `SciFlowSelectionEditorElement` is a companion component for `sciflow-editor`
|
|
7
|
+
* that displays and allows editing of the currently selected element's attributes.
|
|
8
|
+
*
|
|
9
|
+
* It demonstrates how to:
|
|
10
|
+
* - Listen to editor selection changes
|
|
11
|
+
* - Extract node information from the document
|
|
12
|
+
* - Use the command API to update node attributes
|
|
13
|
+
* - Build reactive UI that stays in sync with editor state
|
|
14
|
+
*/
|
|
15
|
+
import { __decorate } from "tslib";
|
|
16
|
+
import { css, html, LitElement, unsafeCSS } from 'lit';
|
|
17
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
18
|
+
import { ref } from 'lit/directives/ref.js';
|
|
19
|
+
import { SourceField } from '@sciflow/editor-core';
|
|
20
|
+
import { getCitationSourceAdapter, } from './citation-source-adapter.js';
|
|
21
|
+
import selectionEditorCss from './selection-editor.css?inline';
|
|
22
|
+
import { applyThemeStylesToRoot, subscribeToSciFlowTheme } from './theme.js';
|
|
23
|
+
/**
|
|
24
|
+
* Custom element that displays and edits the selected element's attributes.
|
|
25
|
+
*
|
|
26
|
+
* Responsibilities:
|
|
27
|
+
* - Resolve the nearest `sciflow-editor` instance (or accept one via property)
|
|
28
|
+
* - Listen to selection changes and extract node information
|
|
29
|
+
* - Display node type, ID, and attributes
|
|
30
|
+
* - Allow editing of attributes through the command API
|
|
31
|
+
*/
|
|
32
|
+
let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends LitElement {
|
|
33
|
+
constructor() {
|
|
34
|
+
super(...arguments);
|
|
35
|
+
/** Reference to the editor element. May be passed directly via property binding. */
|
|
36
|
+
this.editor = null;
|
|
37
|
+
/** Custom adapter for editing citation source. When unset, uses the global adapter. */
|
|
38
|
+
this.citationSourceAdapter = null;
|
|
39
|
+
this.elementInfo = null;
|
|
40
|
+
this.resolvedEditor = null;
|
|
41
|
+
this.themeStyleElements = [];
|
|
42
|
+
this.citationSourceContainer = null;
|
|
43
|
+
}
|
|
44
|
+
static { this.styles = css `${unsafeCSS(selectionEditorCss)}`; }
|
|
45
|
+
connectedCallback() {
|
|
46
|
+
super.connectedCallback();
|
|
47
|
+
this.themeUnsub = subscribeToSciFlowTheme((cssTexts) => {
|
|
48
|
+
this.themeStyleElements = applyThemeStylesToRoot(this.renderRoot, this.themeStyleElements, cssTexts);
|
|
49
|
+
});
|
|
50
|
+
this.resolveEditorReference();
|
|
51
|
+
}
|
|
52
|
+
disconnectedCallback() {
|
|
53
|
+
super.disconnectedCallback();
|
|
54
|
+
this.detachEditorListeners();
|
|
55
|
+
this.themeUnsub?.();
|
|
56
|
+
this.themeUnsub = undefined;
|
|
57
|
+
}
|
|
58
|
+
updated(changedProperties) {
|
|
59
|
+
super.updated(changedProperties);
|
|
60
|
+
if (changedProperties.has('editor') || changedProperties.has('for')) {
|
|
61
|
+
this.resolveEditorReference();
|
|
62
|
+
}
|
|
63
|
+
if (changedProperties.has('elementInfo') &&
|
|
64
|
+
this.citationSourceContainer &&
|
|
65
|
+
this.elementInfo?.type === 'citation' &&
|
|
66
|
+
this.elementInfo.attrs?.source !== undefined) {
|
|
67
|
+
const rawSource = this.elementInfo.attrs.source;
|
|
68
|
+
const items = typeof rawSource === 'string'
|
|
69
|
+
? SourceField.fromString(rawSource)
|
|
70
|
+
: Array.isArray(rawSource)
|
|
71
|
+
? rawSource
|
|
72
|
+
: [];
|
|
73
|
+
const adapter = this.citationSourceAdapter ?? getCitationSourceAdapter();
|
|
74
|
+
const context = {
|
|
75
|
+
applySource: (encoded) => {
|
|
76
|
+
this.updateNodeAttribute('source', encoded);
|
|
77
|
+
return true;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
if (adapter.update) {
|
|
81
|
+
adapter.update(this.citationSourceContainer, items, context);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
render() {
|
|
86
|
+
if (!this.elementInfo || !this.elementInfo.type) {
|
|
87
|
+
return html `
|
|
88
|
+
<div class="selection-editor-container">
|
|
89
|
+
<p class="selection-editor-placeholder">
|
|
90
|
+
No element selected. Click or select an element in the editor.
|
|
91
|
+
</p>
|
|
92
|
+
</div>
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
return html `
|
|
96
|
+
<div class="selection-editor-container">
|
|
97
|
+
<!-- Type display -->
|
|
98
|
+
<div class="selection-editor-field">
|
|
99
|
+
<span class="selection-editor-label">Type:</span>
|
|
100
|
+
<code class="selection-editor-value">${this.elementInfo.type}</code>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!-- ID display (read-only) -->
|
|
104
|
+
${this.elementInfo.id
|
|
105
|
+
? html `
|
|
106
|
+
<div class="selection-editor-field">
|
|
107
|
+
<span class="selection-editor-label">ID:</span>
|
|
108
|
+
<code class="selection-editor-value">${this.elementInfo.id}</code>
|
|
109
|
+
</div>
|
|
110
|
+
`
|
|
111
|
+
: ''}
|
|
112
|
+
|
|
113
|
+
<!-- Attributes (always expanded) -->
|
|
114
|
+
${this.elementInfo.attrs && Object.keys(this.elementInfo.attrs).length > 0
|
|
115
|
+
? html `
|
|
116
|
+
<div class="selection-editor-field">
|
|
117
|
+
<span class="selection-editor-label">Attributes:</span>
|
|
118
|
+
<div class="selection-editor-attrs">
|
|
119
|
+
${this.elementInfo.type === 'citation' &&
|
|
120
|
+
this.elementInfo.attrs?.source !== undefined
|
|
121
|
+
? html `
|
|
122
|
+
<div class="selection-editor-attr-field">
|
|
123
|
+
<span class="selection-editor-label">source:</span>
|
|
124
|
+
<div
|
|
125
|
+
class="citation-source-adapter-root"
|
|
126
|
+
${ref((el) => {
|
|
127
|
+
if (el) {
|
|
128
|
+
this.citationSourceContainer = el;
|
|
129
|
+
this.mountCitationSourceAdapter(el);
|
|
130
|
+
}
|
|
131
|
+
else if (this.citationSourceContainer) {
|
|
132
|
+
this.unmountCitationSourceAdapter(this.citationSourceContainer);
|
|
133
|
+
this.citationSourceContainer = null;
|
|
134
|
+
}
|
|
135
|
+
})}
|
|
136
|
+
></div>
|
|
137
|
+
</div>
|
|
138
|
+
`
|
|
139
|
+
: ''}
|
|
140
|
+
${Object.entries(this.elementInfo.attrs)
|
|
141
|
+
.filter(([key]) => key !== 'id' &&
|
|
142
|
+
key !== 'data' &&
|
|
143
|
+
!(this.elementInfo.type === 'citation' && key === 'source'))
|
|
144
|
+
.map(([attrName, attrValue]) => this.renderAttributeField(attrName, attrValue))}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
`
|
|
148
|
+
: ''}
|
|
149
|
+
</div>
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Find the target editor element and attach listeners for selection changes.
|
|
154
|
+
*/
|
|
155
|
+
resolveEditorReference() {
|
|
156
|
+
const previous = this.resolvedEditor;
|
|
157
|
+
const next = this.resolveEditor();
|
|
158
|
+
if (previous === next && this.selectionListener) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.detachEditorListeners();
|
|
162
|
+
this.resolvedEditor = next;
|
|
163
|
+
if (!next) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.selectionListener = (event) => {
|
|
167
|
+
const selection = event.detail;
|
|
168
|
+
this.updateElementInfo(selection);
|
|
169
|
+
};
|
|
170
|
+
this.readyListener = () => {
|
|
171
|
+
// Re-evaluate selection when editor becomes ready
|
|
172
|
+
if (this.resolvedEditor?.editorView) {
|
|
173
|
+
const selection = this.resolvedEditor.editorView.state.selection;
|
|
174
|
+
this.updateElementInfo({
|
|
175
|
+
anchor: selection.anchor,
|
|
176
|
+
head: selection.head,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
next.addEventListener('editor-selection-change', this.selectionListener);
|
|
181
|
+
next.addEventListener('editor-ready', this.readyListener);
|
|
182
|
+
// Initial evaluation
|
|
183
|
+
if (next.editorView) {
|
|
184
|
+
const selection = next.editorView.state.selection;
|
|
185
|
+
this.updateElementInfo({
|
|
186
|
+
anchor: selection.anchor,
|
|
187
|
+
head: selection.head,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
resolveEditor() {
|
|
192
|
+
if (this.editor) {
|
|
193
|
+
return this.editor;
|
|
194
|
+
}
|
|
195
|
+
// When using declarative binding, walk the DOM to locate the target editor.
|
|
196
|
+
if (this.for) {
|
|
197
|
+
const root = this.getRootNode();
|
|
198
|
+
const candidate = root.getElementById(this.for);
|
|
199
|
+
if (candidate) {
|
|
200
|
+
return candidate;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const nearest = this.closest('sciflow-editor');
|
|
204
|
+
if (nearest) {
|
|
205
|
+
return nearest;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
/** Clean up listeners attached to the last editor instance. */
|
|
210
|
+
detachEditorListeners() {
|
|
211
|
+
if (this.resolvedEditor) {
|
|
212
|
+
if (this.selectionListener) {
|
|
213
|
+
this.resolvedEditor.removeEventListener('editor-selection-change', this.selectionListener);
|
|
214
|
+
}
|
|
215
|
+
if (this.readyListener) {
|
|
216
|
+
this.resolvedEditor.removeEventListener('editor-ready', this.readyListener);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
this.selectionListener = undefined;
|
|
220
|
+
this.readyListener = undefined;
|
|
221
|
+
this.resolvedEditor = null;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Extract element information from the current selection.
|
|
225
|
+
*/
|
|
226
|
+
updateElementInfo(selection) {
|
|
227
|
+
const view = this.resolvedEditor?.editorView;
|
|
228
|
+
if (!view) {
|
|
229
|
+
this.elementInfo = null;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
let elementInfo = null;
|
|
233
|
+
let nodePosition = null;
|
|
234
|
+
const anchor = selection?.anchor ?? 0;
|
|
235
|
+
// Try to get the node at the anchor position
|
|
236
|
+
const nodeAtPos = view.state.doc.nodeAt(anchor);
|
|
237
|
+
if (nodeAtPos && !nodeAtPos.isText) {
|
|
238
|
+
// Node directly at position (e.g., block nodes like heading, paragraph)
|
|
239
|
+
nodePosition = anchor;
|
|
240
|
+
elementInfo = {
|
|
241
|
+
type: nodeAtPos.type.name,
|
|
242
|
+
id: nodeAtPos.attrs?.id ?? null,
|
|
243
|
+
attrs: nodeAtPos.attrs ?? null,
|
|
244
|
+
position: nodePosition,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// If no block node at position, resolve the position and find the parent block node
|
|
249
|
+
const $pos = view.state.doc.resolve(anchor);
|
|
250
|
+
// Walk up the node tree to find the nearest block-level node
|
|
251
|
+
for (let depth = $pos.depth; depth > 0; depth--) {
|
|
252
|
+
const node = $pos.node(depth);
|
|
253
|
+
// Check if this is a block-level node (not doc, not inline)
|
|
254
|
+
if (node.type !== view.state.schema.topNodeType && node.type.spec.group === 'block') {
|
|
255
|
+
nodePosition = $pos.before(depth);
|
|
256
|
+
elementInfo = {
|
|
257
|
+
type: node.type.name,
|
|
258
|
+
id: node.attrs?.id ?? null,
|
|
259
|
+
attrs: node.attrs ?? null,
|
|
260
|
+
position: nodePosition,
|
|
261
|
+
};
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Fallback: if we didn't find a block node, use the immediate parent (excluding doc)
|
|
266
|
+
if (!elementInfo && $pos.parent && $pos.parent.type !== view.state.schema.topNodeType) {
|
|
267
|
+
nodePosition = $pos.before($pos.depth);
|
|
268
|
+
elementInfo = {
|
|
269
|
+
type: $pos.parent.type.name,
|
|
270
|
+
id: $pos.parent.attrs?.id ?? null,
|
|
271
|
+
attrs: $pos.parent.attrs ?? null,
|
|
272
|
+
position: nodePosition,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
this.elementInfo = elementInfo;
|
|
277
|
+
}
|
|
278
|
+
/** Convenience getter mirroring `editor.getCommands()`. */
|
|
279
|
+
getCommandRunner() {
|
|
280
|
+
return this.resolvedEditor?.commands ?? null;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Render a single attribute field, decoding known encoded values for readability.
|
|
284
|
+
*/
|
|
285
|
+
renderAttributeField(attrName, attrValue) {
|
|
286
|
+
const decodedValue = this.decodeSpecialAttribute(attrName, attrValue);
|
|
287
|
+
const renderedValue = decodedValue === null || decodedValue === undefined ? '' : String(decodedValue);
|
|
288
|
+
return html `
|
|
289
|
+
<div class="selection-editor-attr-field">
|
|
290
|
+
<label
|
|
291
|
+
class="selection-editor-attr-label"
|
|
292
|
+
for="selection-editor-attr-${attrName}"
|
|
293
|
+
>
|
|
294
|
+
${attrName}:
|
|
295
|
+
</label>
|
|
296
|
+
<input
|
|
297
|
+
type="text"
|
|
298
|
+
id="selection-editor-attr-${attrName}"
|
|
299
|
+
class="selection-editor-input"
|
|
300
|
+
.value=${renderedValue}
|
|
301
|
+
placeholder="No value"
|
|
302
|
+
@change=${(e) => this.handleAttrChange(e, attrName)}
|
|
303
|
+
/>
|
|
304
|
+
</div>
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Decode known encoded attributes for more readable display/editing.
|
|
309
|
+
*/
|
|
310
|
+
decodeSpecialAttribute(attrName, value) {
|
|
311
|
+
if (attrName === 'source') {
|
|
312
|
+
try {
|
|
313
|
+
const items = typeof value === 'string'
|
|
314
|
+
? SourceField.fromString(value)
|
|
315
|
+
: Array.isArray(value)
|
|
316
|
+
? value
|
|
317
|
+
: [];
|
|
318
|
+
return items.length > 0 ? JSON.stringify(items) : '';
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return value;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Mount the citation source adapter UI when a citation node is selected.
|
|
328
|
+
*/
|
|
329
|
+
mountCitationSourceAdapter(container) {
|
|
330
|
+
if (!this.elementInfo || this.elementInfo.type !== 'citation') {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const rawSource = this.elementInfo.attrs?.source;
|
|
334
|
+
const items = typeof rawSource === 'string'
|
|
335
|
+
? SourceField.fromString(rawSource)
|
|
336
|
+
: Array.isArray(rawSource)
|
|
337
|
+
? rawSource
|
|
338
|
+
: [];
|
|
339
|
+
const adapter = this.citationSourceAdapter ?? getCitationSourceAdapter();
|
|
340
|
+
const context = {
|
|
341
|
+
applySource: (encoded) => {
|
|
342
|
+
this.updateNodeAttribute('source', encoded);
|
|
343
|
+
return true;
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
adapter.render(container, items, context);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Unmount the citation source adapter and clean up.
|
|
350
|
+
*/
|
|
351
|
+
unmountCitationSourceAdapter(container) {
|
|
352
|
+
const adapter = this.citationSourceAdapter ?? getCitationSourceAdapter();
|
|
353
|
+
if (adapter.destroy) {
|
|
354
|
+
adapter.destroy(container);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
container.innerHTML = '';
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Handle attribute field changes.
|
|
362
|
+
*/
|
|
363
|
+
handleAttrChange(event, attrName) {
|
|
364
|
+
const input = event.target;
|
|
365
|
+
const value = input.value.trim();
|
|
366
|
+
let parsedValue = value;
|
|
367
|
+
if (attrName === 'source') {
|
|
368
|
+
parsedValue = this.encodeSourceField(value);
|
|
369
|
+
}
|
|
370
|
+
else if (value !== '') {
|
|
371
|
+
const numValue = parseFloat(value);
|
|
372
|
+
if (!isNaN(numValue) && isFinite(numValue)) {
|
|
373
|
+
parsedValue = numValue;
|
|
374
|
+
if (Number.isInteger(parsedValue)) {
|
|
375
|
+
parsedValue = parseInt(value, 10);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
parsedValue = null;
|
|
381
|
+
}
|
|
382
|
+
this.updateNodeAttribute(attrName, parsedValue);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Update node attributes using the command API.
|
|
386
|
+
* Preserves the 'data' attribute if it exists, even though it's not displayed.
|
|
387
|
+
*/
|
|
388
|
+
updateNodeAttribute(attrName, attrValue) {
|
|
389
|
+
if (!this.elementInfo || this.elementInfo.position === null) {
|
|
390
|
+
console.warn('[sciflow-selection-editor] Cannot update: no element info or position');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const runner = this.getCommandRunner();
|
|
394
|
+
if (!runner) {
|
|
395
|
+
console.warn('[sciflow-selection-editor] Cannot update: no command runner');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
// Prepare the updated attributes object
|
|
400
|
+
// The setNodeAttrs command merges with existing attributes, so 'data' will be preserved
|
|
401
|
+
const updatedAttrs = {
|
|
402
|
+
[attrName]: attrValue,
|
|
403
|
+
};
|
|
404
|
+
// Use the editor command API to update node attributes
|
|
405
|
+
// The command implementation merges with existing attrs, preserving 'data' and other attributes
|
|
406
|
+
const setNodeAttrsCommand = runner.commands.setNodeAttrs;
|
|
407
|
+
if (!setNodeAttrsCommand) {
|
|
408
|
+
console.warn('[sciflow-selection-editor] setNodeAttrs command not available');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const success = setNodeAttrsCommand(this.elementInfo.position, updatedAttrs);
|
|
412
|
+
if (success && this.resolvedEditor?.editorView) {
|
|
413
|
+
const selection = this.resolvedEditor.editorView.state.selection;
|
|
414
|
+
this.updateElementInfo({
|
|
415
|
+
anchor: selection.anchor,
|
|
416
|
+
head: selection.head,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
else if (!success) {
|
|
420
|
+
console.warn('[sciflow-selection-editor] Failed to update node attribute');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
console.error('[sciflow-selection-editor] Failed to update node attribute:', error);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Encode a source string/JSON into the URI-encoded source field format.
|
|
429
|
+
*/
|
|
430
|
+
encodeSourceField(raw) {
|
|
431
|
+
if (!raw) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
const parsed = JSON.parse(raw);
|
|
436
|
+
if (Array.isArray(parsed)) {
|
|
437
|
+
return SourceField.toString(parsed) ?? null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
// Not JSON; fall through.
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
const normalized = SourceField.fromString(raw);
|
|
445
|
+
return SourceField.toString(normalized);
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return raw;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
__decorate([
|
|
453
|
+
property({ attribute: false })
|
|
454
|
+
], SciFlowSelectionEditorElement.prototype, "editor", void 0);
|
|
455
|
+
__decorate([
|
|
456
|
+
property({ type: String, attribute: 'for' })
|
|
457
|
+
], SciFlowSelectionEditorElement.prototype, "for", void 0);
|
|
458
|
+
__decorate([
|
|
459
|
+
property({ attribute: false })
|
|
460
|
+
], SciFlowSelectionEditorElement.prototype, "citationSourceAdapter", void 0);
|
|
461
|
+
__decorate([
|
|
462
|
+
state()
|
|
463
|
+
], SciFlowSelectionEditorElement.prototype, "elementInfo", void 0);
|
|
464
|
+
SciFlowSelectionEditorElement = __decorate([
|
|
465
|
+
customElement('sciflow-selection-editor')
|
|
466
|
+
], SciFlowSelectionEditorElement);
|
|
467
|
+
export { SciFlowSelectionEditorElement };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ThemeStyleInput = string | string[] | CSSStyleSheet | CSSStyleSheet[] | null;
|
|
2
|
+
type ThemeSubscriber = (cssTexts: string[]) => void;
|
|
3
|
+
export declare const setSciFlowThemeStyles: (styles: ThemeStyleInput) => void;
|
|
4
|
+
export declare const getSciFlowThemeStyles: () => string[];
|
|
5
|
+
export declare const subscribeToSciFlowTheme: (callback: ThemeSubscriber) => (() => void);
|
|
6
|
+
export declare const applyThemeStylesToRoot: (root: ShadowRoot | HTMLElement | undefined, existing: HTMLStyleElement[], cssTexts: string[]) => HTMLStyleElement[];
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,aAAa,GAAG,aAAa,EAAE,GAAG,IAAI,CAAC;AAEzF,KAAK,eAAe,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAKpD,eAAO,MAAM,qBAAqB,GAAI,QAAQ,eAAe,KAAG,IAG/D,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAO,MAAM,EAA0B,CAAC;AAE1E,eAAO,MAAM,uBAAuB,GAAI,UAAU,eAAe,KAAG,CAAC,MAAM,IAAI,CAM9E,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,MAAM,UAAU,GAAG,WAAW,GAAG,SAAS,EAC1C,UAAU,gBAAgB,EAAE,EAC5B,UAAU,MAAM,EAAE,KACjB,gBAAgB,EAyBlB,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const subscribers = new Set();
|
|
2
|
+
let currentCssTexts = [];
|
|
3
|
+
export const setSciFlowThemeStyles = (styles) => {
|
|
4
|
+
currentCssTexts = normalizeThemeStyles(styles);
|
|
5
|
+
subscribers.forEach((callback) => callback([...currentCssTexts]));
|
|
6
|
+
};
|
|
7
|
+
export const getSciFlowThemeStyles = () => [...currentCssTexts];
|
|
8
|
+
export const subscribeToSciFlowTheme = (callback) => {
|
|
9
|
+
subscribers.add(callback);
|
|
10
|
+
callback([...currentCssTexts]);
|
|
11
|
+
return () => {
|
|
12
|
+
subscribers.delete(callback);
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export const applyThemeStylesToRoot = (root, existing, cssTexts) => {
|
|
16
|
+
if (!root || typeof root.appendChild !== 'function') {
|
|
17
|
+
return existing;
|
|
18
|
+
}
|
|
19
|
+
for (const styleEl of existing) {
|
|
20
|
+
if (styleEl.parentNode === root) {
|
|
21
|
+
root.removeChild(styleEl);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (cssTexts.length === 0) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const next = [];
|
|
28
|
+
for (const cssText of cssTexts) {
|
|
29
|
+
const styleEl = document.createElement('style');
|
|
30
|
+
styleEl.setAttribute('data-sciflow-theme', 'true');
|
|
31
|
+
styleEl.textContent = cssText;
|
|
32
|
+
root.appendChild(styleEl);
|
|
33
|
+
next.push(styleEl);
|
|
34
|
+
}
|
|
35
|
+
return next;
|
|
36
|
+
};
|
|
37
|
+
const normalizeThemeStyles = (styles) => {
|
|
38
|
+
if (!styles) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const list = Array.isArray(styles) ? styles : [styles];
|
|
42
|
+
const cssTexts = [];
|
|
43
|
+
for (const entry of list) {
|
|
44
|
+
if (!entry) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (typeof entry === 'string') {
|
|
48
|
+
const trimmed = entry.trim();
|
|
49
|
+
if (trimmed) {
|
|
50
|
+
cssTexts.push(trimmed);
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (typeof CSSStyleSheet !== 'undefined' && entry instanceof CSSStyleSheet) {
|
|
55
|
+
const serialized = extractCssFromSheet(entry);
|
|
56
|
+
if (serialized) {
|
|
57
|
+
cssTexts.push(serialized);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return cssTexts;
|
|
62
|
+
};
|
|
63
|
+
const extractCssFromSheet = (sheet) => {
|
|
64
|
+
try {
|
|
65
|
+
const rules = Array.from(sheet.cssRules ?? []);
|
|
66
|
+
const cssText = rules.map((rule) => rule.cssText).join('\n');
|
|
67
|
+
return cssText.trim() ? cssText : null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
};
|