@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,491 @@
1
+ import { __decorate } from "tslib";
2
+ import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
+ import { classMap } from 'lit/directives/class-map.js';
5
+ import { SourceField, } from '@sciflow/editor-core';
6
+ import outlineStyles from './outline.css?inline';
7
+ import { applyThemeStylesToRoot, subscribeToSciFlowTheme } from './theme.js';
8
+ const CLICK_BEHAVIORS = ['none', 'select', 'scroll'];
9
+ const parseLevelsFromAttribute = (value) => {
10
+ if (Array.isArray(value)) {
11
+ const cleaned = value
12
+ .map((entry) => (typeof entry === 'number' ? entry : Number.parseInt(String(entry), 10)))
13
+ .filter((entry) => Number.isFinite(entry) && entry > 0);
14
+ return cleaned.length ? Array.from(new Set(cleaned)) : null;
15
+ }
16
+ if (typeof value !== 'string') {
17
+ return null;
18
+ }
19
+ const tokens = value
20
+ .split(',')
21
+ .map((token) => token.trim())
22
+ .filter(Boolean);
23
+ const levels = [];
24
+ for (const token of tokens) {
25
+ const rangeParts = token.split('-').map((part) => Number.parseInt(part.trim(), 10));
26
+ if (rangeParts.length === 2 && Number.isFinite(rangeParts[0]) && Number.isFinite(rangeParts[1])) {
27
+ const [start, end] = rangeParts;
28
+ const lower = Math.min(start, end);
29
+ const upper = Math.max(start, end);
30
+ for (let level = lower; level <= upper; level += 1) {
31
+ levels.push(level);
32
+ }
33
+ continue;
34
+ }
35
+ const single = Number.parseInt(token, 10);
36
+ if (Number.isFinite(single) && single > 0) {
37
+ levels.push(single);
38
+ }
39
+ }
40
+ const unique = Array.from(new Set(levels));
41
+ return unique.length ? unique : null;
42
+ };
43
+ /**
44
+ * Collect heading and citation metadata from a SciFlow document snapshot.
45
+ * When a ProseMirror document is available, positions and node boundaries are
46
+ * included so navigation and selection syncing can be precise.
47
+ */
48
+ export function collectDocumentOutline(doc, pmDoc = null) {
49
+ const outline = {
50
+ headings: [],
51
+ citations: [],
52
+ };
53
+ // Fast path: traverse the live ProseMirror document for accurate positions.
54
+ if (pmDoc) {
55
+ pmDoc.descendants((node, position) => {
56
+ if (node.type.name === 'heading') {
57
+ outline.headings.push({
58
+ text: node.textContent.trim(),
59
+ level: node.attrs?.level ?? null,
60
+ id: node.attrs?.id ?? null,
61
+ position,
62
+ end: position + node.nodeSize,
63
+ });
64
+ }
65
+ if (node.type.name === 'citation') {
66
+ outline.citations.push({
67
+ id: node.attrs?.id ?? null,
68
+ source: SourceField.fromString(node.attrs?.source ?? null),
69
+ });
70
+ }
71
+ return true;
72
+ });
73
+ return outline;
74
+ }
75
+ if (!doc) {
76
+ return outline;
77
+ }
78
+ const walk = (node) => {
79
+ if (!node) {
80
+ return;
81
+ }
82
+ const { type, attrs = {}, content = [] } = node;
83
+ if (type === 'heading') {
84
+ const headingText = content.map((child) => child?.text ?? '').join('').trim();
85
+ outline.headings.push({
86
+ text: headingText,
87
+ level: attrs.level ?? null,
88
+ id: attrs.id ?? null,
89
+ position: null,
90
+ end: null,
91
+ });
92
+ }
93
+ if (type === 'citation') {
94
+ outline.citations.push({
95
+ id: attrs.id ?? null,
96
+ source: SourceField.fromString(attrs.source ?? null),
97
+ });
98
+ }
99
+ content.forEach((child) => walk(child));
100
+ };
101
+ walk(doc);
102
+ return outline;
103
+ }
104
+ /**
105
+ * Helper used by the demo to expose outline data for debugging.
106
+ */
107
+ export function logDocumentOutline(doc, pmDoc = null) {
108
+ const outline = collectDocumentOutline(doc, pmDoc);
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- instrumentation hook only
110
+ globalThis.__SCIFLOW_OUTLINE__ = outline;
111
+ return outline;
112
+ }
113
+ /**
114
+ * Web component that renders a document outline and stays in sync with a
115
+ * <sciflow-editor> instance. Hosts can either set the `for` attribute to point
116
+ * to an editor ID or pass the editor reference directly via the `editor`
117
+ * property.
118
+ *
119
+ * Configurable behaviors:
120
+ * - `levels` attribute filters which heading levels appear (e.g. "1-3" or "1,2")
121
+ * - `click-behavior` toggles navigation (`none` | `select` | `scroll`)
122
+ * - `renderHeadingLabel` property accepts a custom label renderer
123
+ */
124
+ let SciFlowOutlineElement = class SciFlowOutlineElement extends LitElement {
125
+ constructor() {
126
+ super(...arguments);
127
+ /** Reference to the editor element. May be passed directly via property binding. */
128
+ this.editor = null;
129
+ /** Optional document JSON source when not listening to an editor element. */
130
+ this.doc = null;
131
+ /** Placeholder text displayed when no headings are present. */
132
+ this.emptyText = 'No headings yet.';
133
+ /** Controls click behavior: disabled, set selection, or set selection + scroll. */
134
+ this.clickBehavior = 'scroll';
135
+ /** Filter the outline to specific heading levels (e.g., "1-3" or "1,2,3"). */
136
+ this.levels = null;
137
+ this.headings = [];
138
+ this.activeHeadingKey = null;
139
+ this.resolvedEditor = null;
140
+ this.lastSelection = null;
141
+ this.themeStyleElements = [];
142
+ }
143
+ static { this.styles = css `${unsafeCSS(outlineStyles)}`; }
144
+ connectedCallback() {
145
+ super.connectedCallback();
146
+ this.themeUnsub = subscribeToSciFlowTheme((cssTexts) => {
147
+ this.themeStyleElements = applyThemeStylesToRoot(this.renderRoot, this.themeStyleElements, cssTexts);
148
+ });
149
+ this.resolveEditorReference();
150
+ }
151
+ disconnectedCallback() {
152
+ this.detachEditorListeners();
153
+ this.themeUnsub?.();
154
+ this.themeUnsub = undefined;
155
+ super.disconnectedCallback();
156
+ }
157
+ updated(changedProperties) {
158
+ if (changedProperties.has('for') || changedProperties.has('editor')) {
159
+ this.resolveEditorReference();
160
+ }
161
+ if (changedProperties.has('levels')) {
162
+ this.updateHeadingsFromDoc();
163
+ }
164
+ if (changedProperties.has('doc')) {
165
+ this.updateHeadingsFromDoc(this.doc);
166
+ }
167
+ if (changedProperties.has('clickBehavior')) {
168
+ this.clickBehavior = this.normalizeClickBehavior(this.clickBehavior);
169
+ }
170
+ }
171
+ render() {
172
+ if (!this.headings.length) {
173
+ return html `
174
+ <ol class="outline-list" part="list">
175
+ <li class="outline-placeholder" part="placeholder">${this.emptyText}</li>
176
+ </ol>
177
+ `;
178
+ }
179
+ return html `
180
+ <ol class="outline-list" part="list">
181
+ ${this.headings.map((heading, index) => this.renderHeadingItem(heading, index))}
182
+ </ol>
183
+ `;
184
+ }
185
+ renderHeadingItem(heading, index) {
186
+ const key = this.headingKey(heading, index);
187
+ const isActive = this.activeHeadingKey === key;
188
+ const clickable = this.clickBehavior !== 'none' && heading.position !== null && heading.position !== undefined;
189
+ const label = this.renderHeadingLabel?.(heading, index) ?? heading.text?.trim() ?? 'Untitled section';
190
+ return html `
191
+ <li
192
+ class=${classMap({
193
+ 'outline-item': true,
194
+ 'outline-item--active': isActive,
195
+ [`level-${heading.level ?? 1}`]: Boolean(heading.level),
196
+ })}
197
+ part=${`item${isActive ? ' item-active' : ''}`}
198
+ data-level=${heading.level ?? ''}
199
+ role=${clickable ? 'button' : 'listitem'}
200
+ tabindex=${clickable ? 0 : -1}
201
+ aria-current=${isActive ? 'true' : 'false'}
202
+ @click=${clickable ? () => this.handleHeadingClick(heading, index) : undefined}
203
+ @keydown=${clickable ? (event) => this.handleHeadingKeydown(event, heading, index) : undefined}
204
+ >
205
+ <span class="outline-text" part="text">${label}</span>
206
+ <span class="outline-meta" part="meta">
207
+ ${heading.level ? html `<span part="level">H${heading.level}</span>` : nothing}
208
+ ${heading.id
209
+ ? html `<code class="outline-id" part="id" aria-label=${`Heading id ${heading.id}`}>${heading.id}</code>`
210
+ : nothing}
211
+ </span>
212
+ </li>
213
+ `;
214
+ }
215
+ handleHeadingKeydown(event, heading, index) {
216
+ if (event.key === 'Enter' || event.key === ' ') {
217
+ event.preventDefault();
218
+ this.handleHeadingClick(heading, index);
219
+ }
220
+ }
221
+ handleHeadingClick(heading, index) {
222
+ if (this.clickBehavior === 'none') {
223
+ return;
224
+ }
225
+ const editor = this.resolvedEditor ?? this.editor;
226
+ if (!editor) {
227
+ return;
228
+ }
229
+ if (heading.position === null || heading.position === undefined) {
230
+ return;
231
+ }
232
+ const selectionPosition = this.getHeadingSelectionPosition(heading.position, editor);
233
+ const runner = editor.commands ?? null;
234
+ // The command runner exposes methods directly or via `commands`.
235
+ const commands = runner?.commands ?? null;
236
+ let didSetSelection = false;
237
+ const setSelection = commands?.setSelection;
238
+ if (typeof setSelection === 'function') {
239
+ didSetSelection = setSelection(selectionPosition ?? heading.position, { scroll: false });
240
+ }
241
+ if (didSetSelection) {
242
+ const focusCommand = commands?.focus;
243
+ if (typeof focusCommand === 'function') {
244
+ focusCommand();
245
+ }
246
+ else {
247
+ editor.editorView?.focus();
248
+ }
249
+ if (this.clickBehavior === 'scroll') {
250
+ const scrollCommand = commands?.scrollIntoView;
251
+ if (typeof scrollCommand === 'function') {
252
+ scrollCommand();
253
+ }
254
+ else {
255
+ editor.editorView?.dispatch(editor.editorView.state.tr.scrollIntoView());
256
+ }
257
+ }
258
+ }
259
+ this.activeHeadingKey = this.headingKey(heading, index);
260
+ this.dispatchEvent(new CustomEvent('outline-navigate', {
261
+ detail: {
262
+ heading,
263
+ behavior: this.clickBehavior,
264
+ selectionPosition: selectionPosition ?? heading.position,
265
+ },
266
+ bubbles: true,
267
+ composed: true,
268
+ }));
269
+ }
270
+ resolveEditorReference() {
271
+ const previous = this.resolvedEditor;
272
+ const next = this.resolveEditor();
273
+ if (previous === next && this.changeListener) {
274
+ return;
275
+ }
276
+ this.detachEditorListeners();
277
+ this.resolvedEditor = next;
278
+ if (!next) {
279
+ return;
280
+ }
281
+ this.attachEditorListeners(next);
282
+ // Prime the outline with whatever state is currently available.
283
+ const pmDoc = next.editorView?.state?.doc ?? null;
284
+ const currentDoc = this.doc ?? this.extractDocFromEditor(next);
285
+ this.updateHeadingsFromDoc(currentDoc, pmDoc);
286
+ if (pmDoc && next.editorView) {
287
+ const selection = next.editorView.state.selection;
288
+ this.updateActiveHeading({
289
+ anchor: selection.anchor,
290
+ head: selection.head,
291
+ });
292
+ }
293
+ }
294
+ resolveEditor() {
295
+ if (this.editor) {
296
+ return this.editor;
297
+ }
298
+ if (this.for) {
299
+ const root = this.getRootNode();
300
+ const candidate = root.getElementById(this.for);
301
+ if (candidate) {
302
+ return candidate;
303
+ }
304
+ }
305
+ const nearest = this.closest('sciflow-editor');
306
+ if (nearest) {
307
+ return nearest;
308
+ }
309
+ return null;
310
+ }
311
+ attachEditorListeners(editor) {
312
+ this.changeListener = (event) => {
313
+ const detail = event.detail;
314
+ if (detail?.doc) {
315
+ this.doc = detail.doc;
316
+ }
317
+ const pmDoc = editor.editorView?.state?.doc ?? null;
318
+ this.updateHeadingsFromDoc(detail?.doc ?? this.doc, pmDoc);
319
+ };
320
+ this.selectionListener = (event) => {
321
+ const selection = event.detail;
322
+ this.updateActiveHeading(selection);
323
+ };
324
+ this.readyListener = () => {
325
+ const pmDoc = editor.editorView?.state?.doc ?? null;
326
+ this.updateHeadingsFromDoc(this.doc ?? this.extractDocFromEditor(editor), pmDoc);
327
+ if (pmDoc) {
328
+ const selection = editor.editorView?.state.selection;
329
+ if (selection) {
330
+ this.updateActiveHeading({
331
+ anchor: selection.anchor,
332
+ head: selection.head,
333
+ });
334
+ }
335
+ }
336
+ };
337
+ editor.addEventListener('editor-change', this.changeListener);
338
+ editor.addEventListener('editor-selection-change', this.selectionListener);
339
+ editor.addEventListener('editor-ready', this.readyListener);
340
+ }
341
+ detachEditorListeners() {
342
+ if (this.resolvedEditor) {
343
+ if (this.changeListener) {
344
+ this.resolvedEditor.removeEventListener('editor-change', this.changeListener);
345
+ }
346
+ if (this.selectionListener) {
347
+ this.resolvedEditor.removeEventListener('editor-selection-change', this.selectionListener);
348
+ }
349
+ if (this.readyListener) {
350
+ this.resolvedEditor.removeEventListener('editor-ready', this.readyListener);
351
+ }
352
+ }
353
+ this.changeListener = undefined;
354
+ this.selectionListener = undefined;
355
+ this.readyListener = undefined;
356
+ this.resolvedEditor = null;
357
+ }
358
+ updateHeadingsFromDoc(doc = this.doc, pmDoc = this.resolvedEditor?.editorView?.state?.doc ?? null) {
359
+ const outline = collectDocumentOutline(doc, pmDoc);
360
+ const filtered = this.applyLevelFilter(outline.headings);
361
+ this.headings = filtered;
362
+ this.updateActiveHeading(this.lastSelection);
363
+ this.dispatchOutlineChange(filtered);
364
+ }
365
+ applyLevelFilter(headings) {
366
+ const levelSet = this.getLevelFilterSet();
367
+ if (!levelSet) {
368
+ return headings;
369
+ }
370
+ return headings.filter((heading) => heading.level !== null && levelSet.has(heading.level));
371
+ }
372
+ getLevelFilterSet() {
373
+ if (!this.levels || this.levels.length === 0) {
374
+ return null;
375
+ }
376
+ const set = new Set();
377
+ for (const level of this.levels) {
378
+ const normalized = Math.trunc(level);
379
+ if (Number.isFinite(normalized) && normalized > 0) {
380
+ set.add(normalized);
381
+ }
382
+ }
383
+ return set.size ? set : null;
384
+ }
385
+ updateActiveHeading(selection = this.lastSelection) {
386
+ if (selection) {
387
+ this.lastSelection = selection;
388
+ }
389
+ const candidate = this.findHeadingForSelection(selection ?? null);
390
+ this.activeHeadingKey = candidate ? this.headingKey(candidate) : null;
391
+ }
392
+ findHeadingForSelection(selection) {
393
+ if (!selection || !this.headings.length) {
394
+ return null;
395
+ }
396
+ const position = Math.min(selection.anchor, selection.head);
397
+ let fallback = null;
398
+ for (const heading of this.headings) {
399
+ if (heading.position === null || heading.position === undefined) {
400
+ continue;
401
+ }
402
+ const start = heading.position;
403
+ const end = heading.end ?? Number.POSITIVE_INFINITY;
404
+ if (position >= start && position < end) {
405
+ return heading;
406
+ }
407
+ if (start <= position) {
408
+ fallback = heading;
409
+ }
410
+ }
411
+ return fallback;
412
+ }
413
+ headingKey(heading, index = this.headings.indexOf(heading)) {
414
+ if (heading.id) {
415
+ return heading.id;
416
+ }
417
+ if (heading.position !== null && heading.position !== undefined) {
418
+ return `pos-${heading.position}`;
419
+ }
420
+ return `idx-${index}`;
421
+ }
422
+ getHeadingSelectionPosition(position, editor) {
423
+ if (!Number.isFinite(position)) {
424
+ return null;
425
+ }
426
+ const doc = editor.editorView?.state?.doc;
427
+ if (!doc) {
428
+ return position;
429
+ }
430
+ const node = doc.nodeAt(position);
431
+ if (node?.isTextblock) {
432
+ const insidePosition = position + 1;
433
+ const maxPosition = doc.content.size;
434
+ return insidePosition <= maxPosition ? insidePosition : maxPosition;
435
+ }
436
+ return position;
437
+ }
438
+ extractDocFromEditor(editor) {
439
+ const pmDoc = editor.editorView?.state?.doc;
440
+ return pmDoc ? pmDoc.toJSON() : null;
441
+ }
442
+ normalizeClickBehavior(value) {
443
+ return CLICK_BEHAVIORS.includes(value) ? value : 'scroll';
444
+ }
445
+ dispatchOutlineChange(headings) {
446
+ this.dispatchEvent(new CustomEvent('outline-change', {
447
+ detail: {
448
+ headings,
449
+ active: this.activeHeadingKey,
450
+ },
451
+ bubbles: true,
452
+ composed: true,
453
+ }));
454
+ }
455
+ };
456
+ __decorate([
457
+ property({ attribute: false })
458
+ ], SciFlowOutlineElement.prototype, "editor", void 0);
459
+ __decorate([
460
+ property({ type: String, attribute: 'for' })
461
+ ], SciFlowOutlineElement.prototype, "for", void 0);
462
+ __decorate([
463
+ property({ attribute: false })
464
+ ], SciFlowOutlineElement.prototype, "doc", void 0);
465
+ __decorate([
466
+ property({ type: String, attribute: 'empty-text' })
467
+ ], SciFlowOutlineElement.prototype, "emptyText", void 0);
468
+ __decorate([
469
+ property({ type: String, attribute: 'click-behavior' })
470
+ ], SciFlowOutlineElement.prototype, "clickBehavior", void 0);
471
+ __decorate([
472
+ property({
473
+ attribute: 'levels',
474
+ converter: {
475
+ fromAttribute: (value) => parseLevelsFromAttribute(value),
476
+ },
477
+ })
478
+ ], SciFlowOutlineElement.prototype, "levels", void 0);
479
+ __decorate([
480
+ property({ attribute: false })
481
+ ], SciFlowOutlineElement.prototype, "renderHeadingLabel", void 0);
482
+ __decorate([
483
+ state()
484
+ ], SciFlowOutlineElement.prototype, "headings", void 0);
485
+ __decorate([
486
+ state()
487
+ ], SciFlowOutlineElement.prototype, "activeHeadingKey", void 0);
488
+ SciFlowOutlineElement = __decorate([
489
+ customElement('sciflow-outline')
490
+ ], SciFlowOutlineElement);
491
+ export { SciFlowOutlineElement };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @module reference-list
3
+ *
4
+ * Developer overview:
5
+ * --------------------
6
+ * `SciFlowReferenceListElement` renders a draggable, highlighted list of
7
+ * references. It replaces the bespoke demo sidebar logic so integrators can
8
+ * reuse the same markup and drag payloads in their own UIs.
9
+ */
10
+ import { LitElement } from 'lit';
11
+ type ReferenceAuthor = {
12
+ family?: string | null;
13
+ given?: string | null;
14
+ } | null;
15
+ export type ReferenceListEntry = {
16
+ id?: string | null;
17
+ rawCitation?: string | null;
18
+ raw_citation?: string | null;
19
+ author?: ReferenceAuthor[] | null;
20
+ issued?: {
21
+ 'date-parts'?: Array<Array<number | string | null> | null> | null;
22
+ } | null;
23
+ title?: string | null;
24
+ publisher?: string | null;
25
+ 'container-title'?: string | null;
26
+ 'publisher-place'?: string | null;
27
+ [key: string]: unknown;
28
+ };
29
+ export declare class SciFlowReferenceListElement extends LitElement {
30
+ /** References to render. */
31
+ references: ReferenceListEntry[] | null;
32
+ /** Reference IDs to highlight. */
33
+ highlightedIds: string[];
34
+ /** Text displayed when no references are available. */
35
+ emptyText: string;
36
+ private themeUnsub?;
37
+ private themeStyleElements;
38
+ static styles: import("lit").CSSResult;
39
+ connectedCallback(): void;
40
+ disconnectedCallback(): void;
41
+ /**
42
+ * Highlight the supplied reference ids.
43
+ *
44
+ * @param referenceIds IDs to highlight.
45
+ */
46
+ highlight(referenceIds?: Iterable<string>): void;
47
+ protected render(): import("lit-html").TemplateResult<1>;
48
+ private formatReference;
49
+ private formatAuthor;
50
+ private handleDragStart;
51
+ }
52
+ declare global {
53
+ interface HTMLElementTagNameMap {
54
+ 'sciflow-reference-list': SciFlowReferenceListElement;
55
+ }
56
+ }
57
+ export {};
58
+ //# sourceMappingURL=reference-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reference-list.d.ts","sourceRoot":"","sources":["../../src/lib/reference-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAa,UAAU,EAAsB,MAAM,KAAK,CAAC;AAMhE,KAAK,eAAe,GAAG;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,GAAG,IAAI,CAAC;AAET,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACtF,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,qBACa,2BAA4B,SAAQ,UAAU;IACzD,4BAA4B;IAE5B,UAAU,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAAQ;IAE/C,kCAAkC;IAElC,cAAc,EAAE,MAAM,EAAE,CAAM;IAE9B,uDAAuD;IAEvD,SAAS,SAAwB;IAEjC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,kBAAkB,CAA0B;IAEpD,OAAgB,MAAM,0BAAuC;IAEpD,iBAAiB,IAAI,IAAI;IAWzB,oBAAoB,IAAI,IAAI;IAMrC;;;;OAIG;IACH,SAAS,CAAC,YAAY,GAAE,QAAQ,CAAC,MAAM,CAAM,GAAG,IAAI;cAMjC,MAAM;IA2CzB,OAAO,CAAC,eAAe;IAkCvB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,eAAe;CA+BxB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,wBAAwB,EAAE,2BAA2B,CAAC;KACvD;CACF"}