@sciflow/editor-start 0.0.1-beta → 0.0.2
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 +83 -2
- package/THIRD_PARTY_LICENSES +9 -0
- package/bin/extract-translations.mjs +54 -0
- package/dist/bundle/sciflow-editor.css +1 -1
- package/dist/bundle/sciflow-editor.js +10590 -15324
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/lib/citation-source-adapter.d.ts +22 -2
- package/dist/lib/citation-source-adapter.d.ts.map +1 -1
- package/dist/lib/citation-source-adapter.js +104 -48
- package/dist/lib/drag-preview.d.ts +13 -0
- package/dist/lib/drag-preview.d.ts.map +1 -0
- package/dist/lib/drag-preview.js +43 -0
- package/dist/lib/editor-element.d.ts +30 -4
- package/dist/lib/editor-element.d.ts.map +1 -1
- package/dist/lib/editor-element.js +131 -30
- package/dist/lib/format-bar.d.ts +26 -2
- package/dist/lib/format-bar.d.ts.map +1 -1
- package/dist/lib/format-bar.js +369 -56
- package/dist/lib/ghost-cursor.d.ts +10 -0
- package/dist/lib/ghost-cursor.d.ts.map +1 -0
- package/dist/lib/ghost-cursor.js +59 -0
- package/dist/lib/icons-generated.d.ts +14 -0
- package/dist/lib/icons-generated.d.ts.map +1 -0
- package/dist/lib/icons-generated.js +60 -0
- package/dist/lib/outline.d.ts +18 -10
- package/dist/lib/outline.d.ts.map +1 -1
- package/dist/lib/outline.js +184 -28
- package/dist/lib/reference-list.d.ts +10 -2
- package/dist/lib/reference-list.d.ts.map +1 -1
- package/dist/lib/reference-list.js +47 -9
- package/dist/lib/selection-editor.d.ts +83 -5
- package/dist/lib/selection-editor.d.ts.map +1 -1
- package/dist/lib/selection-editor.js +1225 -147
- package/dist/lib/selection-transform-registry.d.ts +16 -0
- package/dist/lib/selection-transform-registry.d.ts.map +1 -0
- package/dist/lib/selection-transform-registry.js +24 -0
- package/dist/lib/selection-ui-renderer.d.ts +22 -0
- package/dist/lib/selection-ui-renderer.d.ts.map +1 -0
- package/dist/lib/selection-ui-renderer.js +244 -0
- package/dist/lib/selection-widget-host.d.ts +67 -0
- package/dist/lib/selection-widget-host.d.ts.map +1 -0
- package/dist/lib/selection-widget-host.js +151 -0
- package/dist/lib/selection-widget-registry.d.ts +16 -0
- package/dist/lib/selection-widget-registry.d.ts.map +1 -0
- package/dist/lib/selection-widget-registry.js +30 -0
- package/dist/lib/test-fixtures/soak-documents.d.ts +13 -0
- package/dist/lib/test-fixtures/soak-documents.d.ts.map +1 -0
- package/dist/lib/test-fixtures/soak-documents.js +69 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +22 -13
- package/dist/demo/demo.js +0 -56
- package/dist/demo/index.html +0 -169
- package/dist/lib/citation-drop.d.ts +0 -25
- package/dist/lib/citation-drop.d.ts.map +0 -1
- package/dist/lib/citation-drop.js +0 -53
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry for attribute encode/decode transforms used by selection UI.
|
|
3
|
+
* Schema references transforms by string id (e.g. _ui_transform: "citationSourceField").
|
|
4
|
+
*/
|
|
5
|
+
export interface SelectionUiTransform {
|
|
6
|
+
/** Decode stored value (e.g. URI-encoded string) to UI value */
|
|
7
|
+
decode(value: string | null | undefined): unknown;
|
|
8
|
+
/** Encode UI value back to stored form */
|
|
9
|
+
encode(value: unknown): string | null;
|
|
10
|
+
}
|
|
11
|
+
/** Built-in transform id for citation source (URI-encoded JSON). */
|
|
12
|
+
export declare const TRANSFORM_ID_CITATION_SOURCE = "citationSourceField";
|
|
13
|
+
export declare function registerSelectionUiTransform(id: string, transform: SelectionUiTransform): void;
|
|
14
|
+
export declare function getSelectionUiTransform(id: string): SelectionUiTransform | undefined;
|
|
15
|
+
export declare function clearSelectionUiTransforms(): void;
|
|
16
|
+
//# sourceMappingURL=selection-transform-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-transform-registry.d.ts","sourceRoot":"","sources":["../../src/lib/selection-transform-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC;IAClD,0CAA0C;IAC1C,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;CACvC;AAID,oEAAoE;AACpE,eAAO,MAAM,4BAA4B,wBAAwB,CAAC;AAWlE,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,oBAAoB,GAAG,IAAI,CAE9F;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAEpF;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry for attribute encode/decode transforms used by selection UI.
|
|
3
|
+
* Schema references transforms by string id (e.g. _ui_transform: "citationSourceField").
|
|
4
|
+
*/
|
|
5
|
+
import { SourceField } from '@sciflow/editor-core';
|
|
6
|
+
const registry = new Map();
|
|
7
|
+
/** Built-in transform id for citation source (URI-encoded JSON). */
|
|
8
|
+
export const TRANSFORM_ID_CITATION_SOURCE = 'citationSourceField';
|
|
9
|
+
function registerDefaults() {
|
|
10
|
+
registerSelectionUiTransform(TRANSFORM_ID_CITATION_SOURCE, {
|
|
11
|
+
decode: (v) => SourceField.fromString(v),
|
|
12
|
+
encode: (v) => SourceField.toString(Array.isArray(v) ? v : null),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
registerDefaults();
|
|
16
|
+
export function registerSelectionUiTransform(id, transform) {
|
|
17
|
+
registry.set(id, transform);
|
|
18
|
+
}
|
|
19
|
+
export function getSelectionUiTransform(id) {
|
|
20
|
+
return registry.get(id);
|
|
21
|
+
}
|
|
22
|
+
export function clearSelectionUiTransforms() {
|
|
23
|
+
registry.clear();
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders selection editor UI from Draft-07 UI schema fragments.
|
|
3
|
+
* Uses _ui_* annotations for groups, order, widgets, and transforms.
|
|
4
|
+
*/
|
|
5
|
+
import { type TemplateResult } from 'lit';
|
|
6
|
+
import type { SelectionUiSchemaFragment } from '@sciflow/editor-core';
|
|
7
|
+
export interface SelectionUiRenderContext {
|
|
8
|
+
attrs: Record<string, unknown>;
|
|
9
|
+
onAttrChange: (attr: string, value: string | number | null) => void;
|
|
10
|
+
getOptionsSource?: (id: string) => Array<{
|
|
11
|
+
id: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
}>;
|
|
14
|
+
widgetChange?: (path: string, value: unknown) => void;
|
|
15
|
+
showHidden?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Render node attributes from a Draft-07 UI schema fragment.
|
|
19
|
+
* Respects _ui_group, _ui_collapsed, _ui_order, _ui_hidden, _ui_widget, _ui_transform.
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderNodeFromUiSchema(fragment: SelectionUiSchemaFragment, context: SelectionUiRenderContext): TemplateResult;
|
|
22
|
+
//# sourceMappingURL=selection-ui-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-ui-renderer.d.ts","sourceRoot":"","sources":["../../src/lib/selection-ui-renderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAgBtE,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACpE,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACtD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA2LD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,yBAAyB,EACnC,OAAO,EAAE,wBAAwB,GAChC,cAAc,CAqEhB"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders selection editor UI from Draft-07 UI schema fragments.
|
|
3
|
+
* Uses _ui_* annotations for groups, order, widgets, and transforms.
|
|
4
|
+
*/
|
|
5
|
+
import { html } from 'lit';
|
|
6
|
+
import { getUiWidget, getUiGroup, getUiCollapsed, getUiOrder, getUiHidden, getUiOptions, getUiOptionsSource, getUiTransform, t, } from '@sciflow/editor-core';
|
|
7
|
+
import { getSelectionUiTransform } from './selection-transform-registry.js';
|
|
8
|
+
import { getSelectionUiWidgetTag } from './selection-widget-registry.js';
|
|
9
|
+
/**
|
|
10
|
+
* Get display value for an attribute (decode via transform if _ui_transform is set).
|
|
11
|
+
*/
|
|
12
|
+
function getDisplayValue(fragment, attrName, stored, attrs) {
|
|
13
|
+
const transformId = getUiTransform(fragment);
|
|
14
|
+
if (transformId) {
|
|
15
|
+
const transform = getSelectionUiTransform(transformId);
|
|
16
|
+
if (transform) {
|
|
17
|
+
const raw = stored ?? attrs[attrName];
|
|
18
|
+
const str = typeof raw === 'string' ? raw : raw == null ? undefined : String(raw);
|
|
19
|
+
return transform.decode(str);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return stored ?? attrs[attrName];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Encode value for storage (via transform if _ui_transform is set).
|
|
26
|
+
*/
|
|
27
|
+
function encodeValue(fragment, attrName, value) {
|
|
28
|
+
const transformId = getUiTransform(fragment);
|
|
29
|
+
if (transformId) {
|
|
30
|
+
const transform = getSelectionUiTransform(transformId);
|
|
31
|
+
if (transform) {
|
|
32
|
+
const encoded = transform.encode(value);
|
|
33
|
+
return encoded ?? null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (value === undefined || value === null || value === '')
|
|
37
|
+
return null;
|
|
38
|
+
if (typeof value === 'number')
|
|
39
|
+
return value;
|
|
40
|
+
return String(value);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Render one attribute field from schema property fragment.
|
|
44
|
+
*/
|
|
45
|
+
function renderField(attrName, propFragment, context) {
|
|
46
|
+
const label = typeof propFragment.title === 'string' && propFragment.title.trim().length > 0
|
|
47
|
+
? propFragment.title.trim()
|
|
48
|
+
: attrName
|
|
49
|
+
.replace(/[-_]/g, ' ')
|
|
50
|
+
.replace(/\s+/g, ' ')
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/^\w/, (m) => m.toUpperCase());
|
|
53
|
+
const widgetId = getUiWidget(propFragment);
|
|
54
|
+
if (widgetId) {
|
|
55
|
+
const tagName = getSelectionUiWidgetTag(widgetId);
|
|
56
|
+
const value = getDisplayValue(propFragment, attrName, context.attrs[attrName], context.attrs);
|
|
57
|
+
return html `
|
|
58
|
+
<div class="selection-editor-attr-field" data-attr="${attrName}">
|
|
59
|
+
<label class="selection-editor-attr-label">${label}:</label>
|
|
60
|
+
<sciflow-widget-slot
|
|
61
|
+
.widgetTag=${tagName}
|
|
62
|
+
.value=${value}
|
|
63
|
+
.valuePath=${attrName}
|
|
64
|
+
@change=${(e) => {
|
|
65
|
+
const patches = e.detail?.patches ?? [];
|
|
66
|
+
patches.forEach((p) => {
|
|
67
|
+
const v = encodeValue(propFragment, attrName, p.value);
|
|
68
|
+
context.onAttrChange(attrName, v);
|
|
69
|
+
});
|
|
70
|
+
context.widgetChange?.(attrName, patches[0]?.value);
|
|
71
|
+
}}
|
|
72
|
+
></sciflow-widget-slot>
|
|
73
|
+
</div>
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
const options = getUiOptions(propFragment);
|
|
77
|
+
const optionsSourceId = getUiOptionsSource(propFragment);
|
|
78
|
+
const displayValue = getDisplayValue(propFragment, attrName, context.attrs[attrName], context.attrs);
|
|
79
|
+
const rendered = displayValue === null || displayValue === undefined ? '' : String(displayValue);
|
|
80
|
+
if (Array.isArray(options) && options.length === 2 && typeof options[0] === 'number' && typeof options[1] === 'number') {
|
|
81
|
+
const [min, max] = options;
|
|
82
|
+
const num = Math.min(max, Math.max(min, parseInt(rendered, 10) || min));
|
|
83
|
+
const nums = Array.from({ length: max - min + 1 }, (_, i) => min + i);
|
|
84
|
+
return html `
|
|
85
|
+
<div class="selection-editor-attr-field">
|
|
86
|
+
<label class="selection-editor-attr-label" for="selection-editor-attr-${attrName}">${label}:</label>
|
|
87
|
+
<select
|
|
88
|
+
id="selection-editor-attr-${attrName}"
|
|
89
|
+
class="selection-editor-input selection-editor-select"
|
|
90
|
+
.value=${String(num)}
|
|
91
|
+
@change=${(e) => {
|
|
92
|
+
const v = e.target.value;
|
|
93
|
+
context.onAttrChange(attrName, parseInt(v, 10));
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
${nums.map((n) => html `<option value="${String(n)}">${String(n)}</option>`)}
|
|
97
|
+
</select>
|
|
98
|
+
</div>
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(options) && options.length > 0 && !optionsSourceId) {
|
|
102
|
+
const opts = options;
|
|
103
|
+
const normalized = opts
|
|
104
|
+
.map((o) => {
|
|
105
|
+
if (typeof o === 'string' || typeof o === 'number') {
|
|
106
|
+
return { value: o, label: String(o) };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
value: o.value,
|
|
110
|
+
label: o.label ?? String(o.value),
|
|
111
|
+
};
|
|
112
|
+
})
|
|
113
|
+
.filter((o) => o.value !== undefined && o.value !== null);
|
|
114
|
+
const current = rendered;
|
|
115
|
+
return html `
|
|
116
|
+
<div class="selection-editor-attr-field">
|
|
117
|
+
<label class="selection-editor-attr-label" for="selection-editor-attr-${attrName}">${label}:</label>
|
|
118
|
+
<select
|
|
119
|
+
id="selection-editor-attr-${attrName}"
|
|
120
|
+
class="selection-editor-input selection-editor-select"
|
|
121
|
+
.value=${current}
|
|
122
|
+
@change=${(e) => {
|
|
123
|
+
const v = e.target.value;
|
|
124
|
+
const num = parseInt(v, 10);
|
|
125
|
+
const out = !isNaN(num) ? num : (v || null);
|
|
126
|
+
context.onAttrChange(attrName, out);
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
${normalized.map((o) => html `<option value="${String(o.value)}">${o.label}</option>`)}
|
|
130
|
+
</select>
|
|
131
|
+
</div>
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
if (optionsSourceId && context.getOptionsSource) {
|
|
135
|
+
const sourceOptions = context.getOptionsSource(optionsSourceId);
|
|
136
|
+
if (sourceOptions.length > 0) {
|
|
137
|
+
return html `
|
|
138
|
+
<div class="selection-editor-attr-field">
|
|
139
|
+
<label class="selection-editor-attr-label" for="selection-editor-attr-${attrName}">${label}:</label>
|
|
140
|
+
<select
|
|
141
|
+
id="selection-editor-attr-${attrName}"
|
|
142
|
+
class="selection-editor-input selection-editor-select"
|
|
143
|
+
.value=${rendered}
|
|
144
|
+
@change=${(e) => {
|
|
145
|
+
const v = e.target.value.trim() || null;
|
|
146
|
+
context.onAttrChange(attrName, v);
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<option value="">No value</option>
|
|
150
|
+
${sourceOptions.map((o) => html `<option value="${o.id}">${o.label ?? o.id}</option>`)}
|
|
151
|
+
</select>
|
|
152
|
+
</div>
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return html `
|
|
157
|
+
<div class="selection-editor-attr-field">
|
|
158
|
+
<label class="selection-editor-attr-label" for="selection-editor-attr-${attrName}">${label}:</label>
|
|
159
|
+
<input
|
|
160
|
+
type="text"
|
|
161
|
+
id="selection-editor-attr-${attrName}"
|
|
162
|
+
class="selection-editor-input"
|
|
163
|
+
.value=${rendered}
|
|
164
|
+
placeholder="No value"
|
|
165
|
+
@change=${(e) => {
|
|
166
|
+
const v = e.target.value.trim();
|
|
167
|
+
const encoded = encodeValue(propFragment, attrName, v || null);
|
|
168
|
+
context.onAttrChange(attrName, encoded);
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Render node attributes from a Draft-07 UI schema fragment.
|
|
176
|
+
* Respects _ui_group, _ui_collapsed, _ui_order, _ui_hidden, _ui_widget, _ui_transform.
|
|
177
|
+
*/
|
|
178
|
+
export function renderNodeFromUiSchema(fragment, context) {
|
|
179
|
+
const frag = fragment;
|
|
180
|
+
const nodeWidget = getUiWidget(frag);
|
|
181
|
+
if (nodeWidget) {
|
|
182
|
+
const tagName = getSelectionUiWidgetTag(nodeWidget);
|
|
183
|
+
return html `
|
|
184
|
+
<sciflow-widget-slot
|
|
185
|
+
.widgetTag=${tagName}
|
|
186
|
+
.value=${context.attrs}
|
|
187
|
+
.valuePath=${''}
|
|
188
|
+
@change=${(e) => {
|
|
189
|
+
const patches = e.detail?.patches ?? [];
|
|
190
|
+
patches.forEach((p) => {
|
|
191
|
+
if (p.path === '' && typeof p.value === 'object' && p.value !== null) {
|
|
192
|
+
Object.entries(p.value).forEach(([k, v]) => {
|
|
193
|
+
context.onAttrChange(k, v);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
else if (p.path) {
|
|
197
|
+
context.onAttrChange(p.path, p.value);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}}
|
|
201
|
+
></sciflow-widget-slot>
|
|
202
|
+
`;
|
|
203
|
+
}
|
|
204
|
+
const properties = (frag.properties ?? frag);
|
|
205
|
+
if (!properties || typeof properties !== 'object') {
|
|
206
|
+
return html `<div class="selection-editor-attrs"></div>`;
|
|
207
|
+
}
|
|
208
|
+
const entries = Object.entries(properties)
|
|
209
|
+
.filter(([, prop]) => context.showHidden || !getUiHidden(prop))
|
|
210
|
+
.sort(([, a], [, b]) => getUiOrder(a) - getUiOrder(b));
|
|
211
|
+
const primary = [];
|
|
212
|
+
const advanced = { group: 'advanced', collapsed: true, items: [] };
|
|
213
|
+
for (const [attrName, propFragment] of entries) {
|
|
214
|
+
const group = getUiGroup(propFragment) ?? 'primary';
|
|
215
|
+
const collapsed = getUiCollapsed(propFragment);
|
|
216
|
+
const field = renderField(attrName, propFragment, context);
|
|
217
|
+
if (group === 'primary') {
|
|
218
|
+
primary.push(field);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (advanced.items.length === 0)
|
|
222
|
+
advanced.collapsed = collapsed;
|
|
223
|
+
advanced.items.push(field);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (advanced.items.length === 0) {
|
|
227
|
+
return html `
|
|
228
|
+
<div class="selection-editor-attrs">
|
|
229
|
+
${primary}
|
|
230
|
+
</div>
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
return html `
|
|
234
|
+
<div class="selection-editor-attrs">
|
|
235
|
+
${primary}
|
|
236
|
+
<details class="selection-editor-details" ?open=${!advanced.collapsed}>
|
|
237
|
+
<summary class="selection-editor-details-summary">${t('selectionEditor.moreAttributes')}</summary>
|
|
238
|
+
<div class="selection-editor-details-body">
|
|
239
|
+
${advanced.items}
|
|
240
|
+
</div>
|
|
241
|
+
</details>
|
|
242
|
+
</div>
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host for _ui_widget web components in the selection editor.
|
|
3
|
+
* Provides stringified value input and normalizes ui-widget-change output.
|
|
4
|
+
*/
|
|
5
|
+
import { html, LitElement } from 'lit';
|
|
6
|
+
export type WidgetChangePatch = {
|
|
7
|
+
path: string;
|
|
8
|
+
value: unknown;
|
|
9
|
+
};
|
|
10
|
+
export type WidgetChangePayload = string | {
|
|
11
|
+
path: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
} | {
|
|
14
|
+
patches: WidgetChangePatch[];
|
|
15
|
+
};
|
|
16
|
+
export interface NormalizedWidgetChange {
|
|
17
|
+
/** Single path (for attribute or dotted path). Empty string = root value. */
|
|
18
|
+
path: string;
|
|
19
|
+
value: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalize widget event detail to a list of path/value updates.
|
|
23
|
+
* - string -> [{ path: '', value }]
|
|
24
|
+
* - { path, value } -> [{ path, value }]
|
|
25
|
+
* - { patches } -> patches
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeWidgetChange(payload: WidgetChangePayload): NormalizedWidgetChange[];
|
|
28
|
+
export interface MountWidgetHostOptions {
|
|
29
|
+
/** Container to append the widget element into */
|
|
30
|
+
container: HTMLElement;
|
|
31
|
+
/** Custom element tag name (e.g. "sciflow-citation-source-widget") */
|
|
32
|
+
tagName: string;
|
|
33
|
+
/** Current value; will be stringified and set as value attribute/property */
|
|
34
|
+
value: unknown;
|
|
35
|
+
/** Called when the widget dispatches ui-widget-change with normalized patches */
|
|
36
|
+
onChange: (patches: NormalizedWidgetChange[]) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Mount a widget host element, set its value, and listen for ui-widget-change.
|
|
40
|
+
* @returns Unmount function to remove the element and listener
|
|
41
|
+
*/
|
|
42
|
+
export declare function mountWidgetHost(options: MountWidgetHostOptions): () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Update an already-mounted widget's value (e.g. when selection or doc changes).
|
|
45
|
+
*/
|
|
46
|
+
export declare function updateWidgetHostValue(container: HTMLElement, tagName: string, value: unknown): void;
|
|
47
|
+
/** Custom element that mounts a widget host and forwards ui-widget-change. */
|
|
48
|
+
export declare class SciflowWidgetSlotElement extends LitElement {
|
|
49
|
+
static styles: import("lit").CSSResult;
|
|
50
|
+
widgetTag: string;
|
|
51
|
+
value: unknown;
|
|
52
|
+
valuePath: string;
|
|
53
|
+
private unmount;
|
|
54
|
+
private slotEl;
|
|
55
|
+
firstUpdated(): void;
|
|
56
|
+
disconnectedCallback(): void;
|
|
57
|
+
updated(changed: Map<string, unknown>): void;
|
|
58
|
+
private mountWidget;
|
|
59
|
+
private remountWidget;
|
|
60
|
+
render(): ReturnType<typeof html>;
|
|
61
|
+
}
|
|
62
|
+
declare global {
|
|
63
|
+
interface HTMLElementTagNameMap {
|
|
64
|
+
'sciflow-widget-slot': SciflowWidgetSlotElement;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=selection-widget-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-widget-host.d.ts","sourceRoot":"","sources":["../../src/lib/selection-widget-host.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAO,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAG5C,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,mBAAmB,GAC3B,MAAM,GACN;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAChC;IAAE,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAAC;AAErC,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,mBAAmB,GAAG,sBAAsB,EAAE,CAW5F;AAID,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,SAAS,EAAE,WAAW,CAAC;IACvB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,KAAK,EAAE,OAAO,CAAC;IACf,iFAAiF;IACjF,QAAQ,EAAE,CAAC,OAAO,EAAE,sBAAsB,EAAE,KAAK,IAAI,CAAC;CACvD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,IAAI,CAwB3E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,WAAW,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,GACb,IAAI,CAMN;AAED,8EAA8E;AAC9E,qBACa,wBAAyB,SAAQ,UAAU;IACtD,OAAgB,MAAM,0BAEpB;IAEiD,SAAS,SAAM;IAClC,KAAK,EAAE,OAAO,CAAQ;IACP,SAAS,SAAM;IAE9D,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAA+B;IAEpC,YAAY,IAAI,IAAI;IAIpB,oBAAoB,IAAI,IAAI;IAS5B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAWrD,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,aAAa;IAwBZ,MAAM,IAAI,UAAU,CAAC,OAAO,IAAI,CAAC;CAG3C;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,qBAAqB,EAAE,wBAAwB,CAAC;KACjD;CACF"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host for _ui_widget web components in the selection editor.
|
|
3
|
+
* Provides stringified value input and normalizes ui-widget-change output.
|
|
4
|
+
*/
|
|
5
|
+
import { __decorate } from "tslib";
|
|
6
|
+
import { css, html, LitElement } from 'lit';
|
|
7
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
8
|
+
/**
|
|
9
|
+
* Normalize widget event detail to a list of path/value updates.
|
|
10
|
+
* - string -> [{ path: '', value }]
|
|
11
|
+
* - { path, value } -> [{ path, value }]
|
|
12
|
+
* - { patches } -> patches
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeWidgetChange(payload) {
|
|
15
|
+
if (typeof payload === 'string') {
|
|
16
|
+
return [{ path: '', value: payload }];
|
|
17
|
+
}
|
|
18
|
+
if (payload && typeof payload === 'object' && 'patches' in payload && Array.isArray(payload.patches)) {
|
|
19
|
+
return payload.patches.map((p) => ({ path: p.path ?? '', value: p.value }));
|
|
20
|
+
}
|
|
21
|
+
if (payload && typeof payload === 'object' && 'path' in payload && 'value' in payload) {
|
|
22
|
+
return [{ path: payload.path, value: payload.value }];
|
|
23
|
+
}
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const UI_WIDGET_CHANGE = 'ui-widget-change';
|
|
27
|
+
/**
|
|
28
|
+
* Mount a widget host element, set its value, and listen for ui-widget-change.
|
|
29
|
+
* @returns Unmount function to remove the element and listener
|
|
30
|
+
*/
|
|
31
|
+
export function mountWidgetHost(options) {
|
|
32
|
+
const { container, tagName, value, onChange } = options;
|
|
33
|
+
const el = document.createElement(tagName);
|
|
34
|
+
const str = value === undefined || value === null ? '' : JSON.stringify(value);
|
|
35
|
+
el.setAttribute('value', str);
|
|
36
|
+
el.value = str;
|
|
37
|
+
const handler = (e) => {
|
|
38
|
+
const customEvent = e;
|
|
39
|
+
const detail = customEvent.detail;
|
|
40
|
+
if (detail === undefined)
|
|
41
|
+
return;
|
|
42
|
+
const patches = normalizeWidgetChange(detail);
|
|
43
|
+
if (patches.length > 0) {
|
|
44
|
+
onChange(patches);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
el.addEventListener(UI_WIDGET_CHANGE, handler);
|
|
48
|
+
container.appendChild(el);
|
|
49
|
+
return () => {
|
|
50
|
+
el.removeEventListener(UI_WIDGET_CHANGE, handler);
|
|
51
|
+
el.remove();
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Update an already-mounted widget's value (e.g. when selection or doc changes).
|
|
56
|
+
*/
|
|
57
|
+
export function updateWidgetHostValue(container, tagName, value) {
|
|
58
|
+
const el = container.querySelector(tagName);
|
|
59
|
+
if (!el)
|
|
60
|
+
return;
|
|
61
|
+
const str = value === undefined || value === null ? '' : JSON.stringify(value);
|
|
62
|
+
el.setAttribute('value', str);
|
|
63
|
+
el.value = str;
|
|
64
|
+
}
|
|
65
|
+
/** Custom element that mounts a widget host and forwards ui-widget-change. */
|
|
66
|
+
let SciflowWidgetSlotElement = class SciflowWidgetSlotElement extends LitElement {
|
|
67
|
+
constructor() {
|
|
68
|
+
super(...arguments);
|
|
69
|
+
this.widgetTag = '';
|
|
70
|
+
this.value = null;
|
|
71
|
+
this.valuePath = '';
|
|
72
|
+
this.unmount = null;
|
|
73
|
+
this.slotEl = null;
|
|
74
|
+
}
|
|
75
|
+
static { this.styles = css `
|
|
76
|
+
:host { display: block; }
|
|
77
|
+
`; }
|
|
78
|
+
firstUpdated() {
|
|
79
|
+
this.mountWidget();
|
|
80
|
+
}
|
|
81
|
+
disconnectedCallback() {
|
|
82
|
+
if (this.unmount) {
|
|
83
|
+
this.unmount();
|
|
84
|
+
this.unmount = null;
|
|
85
|
+
}
|
|
86
|
+
this.slotEl = null;
|
|
87
|
+
super.disconnectedCallback();
|
|
88
|
+
}
|
|
89
|
+
updated(changed) {
|
|
90
|
+
if (changed.has('widgetTag')) {
|
|
91
|
+
this.remountWidget();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (changed.has('value') && this.slotEl && this.widgetTag && this.unmount) {
|
|
95
|
+
updateWidgetHostValue(this.slotEl, this.widgetTag, this.value);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
mountWidget() {
|
|
99
|
+
const slot = this.renderRoot.querySelector('.sciflow-widget-slot');
|
|
100
|
+
if (!slot)
|
|
101
|
+
return;
|
|
102
|
+
this.slotEl = slot;
|
|
103
|
+
if (!this.widgetTag)
|
|
104
|
+
return;
|
|
105
|
+
this.unmount = mountWidgetHost({
|
|
106
|
+
container: slot,
|
|
107
|
+
tagName: this.widgetTag,
|
|
108
|
+
value: this.value,
|
|
109
|
+
onChange: (patches) => {
|
|
110
|
+
this.dispatchEvent(new CustomEvent('change', { detail: { patches }, bubbles: true, composed: true }));
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
remountWidget() {
|
|
115
|
+
if (this.unmount) {
|
|
116
|
+
this.unmount();
|
|
117
|
+
this.unmount = null;
|
|
118
|
+
}
|
|
119
|
+
if (!this.slotEl || !this.widgetTag) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.unmount = mountWidgetHost({
|
|
123
|
+
container: this.slotEl,
|
|
124
|
+
tagName: this.widgetTag,
|
|
125
|
+
value: this.value,
|
|
126
|
+
onChange: (patches) => {
|
|
127
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
128
|
+
detail: { patches },
|
|
129
|
+
bubbles: true,
|
|
130
|
+
composed: true,
|
|
131
|
+
}));
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
render() {
|
|
136
|
+
return html `<div class="sciflow-widget-slot"></div>`;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
__decorate([
|
|
140
|
+
property({ type: String, attribute: 'tag-name' })
|
|
141
|
+
], SciflowWidgetSlotElement.prototype, "widgetTag", void 0);
|
|
142
|
+
__decorate([
|
|
143
|
+
property({ attribute: false })
|
|
144
|
+
], SciflowWidgetSlotElement.prototype, "value", void 0);
|
|
145
|
+
__decorate([
|
|
146
|
+
property({ type: String, attribute: 'path' })
|
|
147
|
+
], SciflowWidgetSlotElement.prototype, "valuePath", void 0);
|
|
148
|
+
SciflowWidgetSlotElement = __decorate([
|
|
149
|
+
customElement('sciflow-widget-slot')
|
|
150
|
+
], SciflowWidgetSlotElement);
|
|
151
|
+
export { SciflowWidgetSlotElement };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry for _ui_widget custom element tag names.
|
|
3
|
+
* Schema references widgets by string id; the host creates an element with this tag.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Register a widget id to custom element tag name.
|
|
7
|
+
* @param id - Schema _ui_widget value (e.g. "citationSource", "mathTexPreview")
|
|
8
|
+
* @param tagName - Custom element tag (e.g. "sciflow-citation-source-widget")
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerSelectionUiWidget(id: string, tagName: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve widget id to tag name. If id looks like a tag (contains hyphen), return as-is.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getSelectionUiWidgetTag(id: string): string;
|
|
15
|
+
export declare function clearSelectionUiWidgets(): void;
|
|
16
|
+
//# sourceMappingURL=selection-widget-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-widget-registry.d.ts","sourceRoot":"","sources":["../../src/lib/selection-widget-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3E;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAU1D;AAED,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry for _ui_widget custom element tag names.
|
|
3
|
+
* Schema references widgets by string id; the host creates an element with this tag.
|
|
4
|
+
*/
|
|
5
|
+
const registry = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Register a widget id to custom element tag name.
|
|
8
|
+
* @param id - Schema _ui_widget value (e.g. "citationSource", "mathTexPreview")
|
|
9
|
+
* @param tagName - Custom element tag (e.g. "sciflow-citation-source-widget")
|
|
10
|
+
*/
|
|
11
|
+
export function registerSelectionUiWidget(id, tagName) {
|
|
12
|
+
registry.set(id, tagName);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve widget id to tag name. If id looks like a tag (contains hyphen), return as-is.
|
|
16
|
+
*/
|
|
17
|
+
export function getSelectionUiWidgetTag(id) {
|
|
18
|
+
const resolved = registry.get(id);
|
|
19
|
+
if (resolved) {
|
|
20
|
+
return resolved;
|
|
21
|
+
}
|
|
22
|
+
// Allow id to be the tag name itself (e.g. "sciflow-citation-source-widget")
|
|
23
|
+
if (id.includes('-')) {
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
return id;
|
|
27
|
+
}
|
|
28
|
+
export function clearSelectionUiWidgets() {
|
|
29
|
+
registry.clear();
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SciFlowDocJSON } from '@sciflow/editor-core';
|
|
2
|
+
type SoakDocSize = {
|
|
3
|
+
headingCount: number;
|
|
4
|
+
paragraphsPerHeading: number;
|
|
5
|
+
wordsPerParagraph: number;
|
|
6
|
+
figureEvery: number;
|
|
7
|
+
};
|
|
8
|
+
export declare const createSoakDocFixture: (size: SoakDocSize) => SciFlowDocJSON;
|
|
9
|
+
export declare const SMALL_SOAK_DOC: SciFlowDocJSON;
|
|
10
|
+
export declare const MEDIUM_SOAK_DOC: SciFlowDocJSON;
|
|
11
|
+
export declare const LARGE_SOAK_DOC: SciFlowDocJSON;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=soak-documents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"soak-documents.d.ts","sourceRoot":"","sources":["../../../src/lib/test-fixtures/soak-documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,KAAK,WAAW,GAAG;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAoCF,eAAO,MAAM,oBAAoB,GAAI,MAAM,WAAW,KAAG,cAwBxD,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,cAK3B,CAAC;AAEH,eAAO,MAAM,eAAe,EAAE,cAK5B,CAAC;AAEH,eAAO,MAAM,cAAc,EAAE,cAK3B,CAAC"}
|