@sciflow/editor-start 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -75,6 +75,13 @@ export declare class SciFlowFormatBarElement extends LitElement {
75
75
  private selectStyle;
76
76
  private isBlockquoteActive;
77
77
  private canUseCommand;
78
+ /**
79
+ * The trigger shows the localised "Insert" label next to the `+` icon: the
80
+ * word only appeared on the menu items before, leaving sighted users with a
81
+ * bare glyph. The visible label and `aria-label` are read from the same key,
82
+ * so they cannot diverge — and because `aria-label` replaces a button's text
83
+ * content for assistive tech, the word is still announced exactly once.
84
+ */
78
85
  private renderInsertDropdown;
79
86
  private toggleInsertMenu;
80
87
  private executeInsertCommand;
@@ -1 +1 @@
1
- {"version":3,"file":"format-bar.d.ts","sourceRoot":"","sources":["../../src/lib/format-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAa,UAAU,EAAsB,MAAM,KAAK,CAAC;AAOhE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAoChE;;;;;;;;;;;GAWG;AACH,qBACa,uBAAwB,SAAQ,UAAU;IACrD,oFAAoF;IAEpF,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IAE3C,gEAAgE;IAEhE,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;;;;OAQG;IAEH,YAAY,SAAM;IAGlB,OAAO,CAAC,gBAAgB,CAAK;IAG7B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,iBAAiB,CAAC,CAAgB;IAC1C,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,mBAAmB,CAAC,CAA0B;IAEtD,OAAgB,MAAM,0BAAmC;IAEzD,uEAAuE;IAC9D,iBAAiB,IAAI,IAAI;IAoBlC,qDAAqD;IAC5C,oBAAoB,IAAI,IAAI;IAYrC;;;OAGG;cACgB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAMjF;;;OAGG;cACgB,MAAM;IAwCzB,OAAO,CAAC,mBAAmB;IAyD3B,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,oBAAoB;IAuD5B,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,0BAA0B;IAkBlC,OAAO,CAAC,yBAAyB;IAWjC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA2ChC;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA+B9B,OAAO,CAAC,aAAa;IAkBrB,+DAA+D;IAC/D,OAAO,CAAC,qBAAqB;IAc7B,2DAA2D;IAC3D,OAAO,CAAC,gBAAgB;IAQxB,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB;IA6BxB,mDAAmD;IACnD,OAAO,CAAC,kBAAkB;IA6B1B,yEAAyE;IACzE,OAAO,CAAC,mBAAmB;IA6C3B,gFAAgF;IAChF,OAAO,CAAC,iBAAiB;IAmBzB,wEAAwE;IACxE,OAAO,CAAC,kBAAkB;IAmB1B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAoB1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuC3B;;;OAGG;IACH,OAAO,KAAK,cAAc,GAOzB;IAED;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA2BzB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,uBAAuB,CAAC;KAC9C;CACF"}
1
+ {"version":3,"file":"format-bar.d.ts","sourceRoot":"","sources":["../../src/lib/format-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAa,UAAU,EAAsB,MAAM,KAAK,CAAC;AAOhE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAoChE;;;;;;;;;;;GAWG;AACH,qBACa,uBAAwB,SAAQ,UAAU;IACrD,oFAAoF;IAEpF,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IAE3C,gEAAgE;IAEhE,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;;;;OAQG;IAEH,YAAY,SAAM;IAGlB,OAAO,CAAC,gBAAgB,CAAK;IAG7B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,iBAAiB,CAAC,CAAgB;IAC1C,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,mBAAmB,CAAC,CAA0B;IAEtD,OAAgB,MAAM,0BAAmC;IAEzD,uEAAuE;IAC9D,iBAAiB,IAAI,IAAI;IAoBlC,qDAAqD;IAC5C,oBAAoB,IAAI,IAAI;IAYrC;;;OAGG;cACgB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAMjF;;;OAGG;cACgB,MAAM;IAwCzB,OAAO,CAAC,mBAAmB;IAyD3B,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,aAAa;IAUrB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IA8D5B,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,0BAA0B;IAkBlC,OAAO,CAAC,yBAAyB;IAWjC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA2ChC;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA+B9B,OAAO,CAAC,aAAa;IAkBrB,+DAA+D;IAC/D,OAAO,CAAC,qBAAqB;IAc7B,2DAA2D;IAC3D,OAAO,CAAC,gBAAgB;IAQxB,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB;IA6BxB,mDAAmD;IACnD,OAAO,CAAC,kBAAkB;IA6B1B,yEAAyE;IACzE,OAAO,CAAC,mBAAmB;IA6C3B,gFAAgF;IAChF,OAAO,CAAC,iBAAiB;IAmBzB,wEAAwE;IACxE,OAAO,CAAC,kBAAkB;IAmB1B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAoB1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuC3B;;;OAGG;IACH,OAAO,KAAK,cAAc,GAOzB;IAED;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA2BzB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,uBAAuB,CAAC;KAC9C;CACF"}
@@ -265,6 +265,13 @@ let SciFlowFormatBarElement = class SciFlowFormatBarElement extends LitElement {
265
265
  /* --------------------------------------------------------- */
266
266
  /* Insert dropdown (icon + chevron → popover) */
267
267
  /* --------------------------------------------------------- */
268
+ /**
269
+ * The trigger shows the localised "Insert" label next to the `+` icon: the
270
+ * word only appeared on the menu items before, leaving sighted users with a
271
+ * bare glyph. The visible label and `aria-label` are read from the same key,
272
+ * so they cannot diverge — and because `aria-label` replaces a button's text
273
+ * content for assistive tech, the word is still announced exactly once.
274
+ */
268
275
  renderInsertDropdown(runner, metadata) {
269
276
  const availability = runner?.available?.() ?? null;
270
277
  // Collect block-level insert commands (media group + table insert + footnote)
@@ -285,25 +292,32 @@ let SciFlowFormatBarElement = class SciFlowFormatBarElement extends LitElement {
285
292
  @click=${this.toggleInsertMenu}
286
293
  >
287
294
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
295
+ <span class="insert-dropdown-label">${t('formatBar.insert')}</span>
288
296
  <span class="dropdown-chevron">${chevronSvg}</span>
289
297
  </button>
290
298
  ${this.insertMenuOpen
291
299
  ? html `
292
- <div class="insert-dropdown-menu" role="menu">
300
+ <div class="insert-dropdown-menu" role="menu" aria-label=${t('formatBar.insert')}>
293
301
  ${insertCommands.map(([name, meta]) => {
294
302
  const canExecute = availability?.[name] ? availability[name]() : true;
303
+ // The trigger above already says "Insert", so an item shows its
304
+ // short wording when the command offers one ("Figure", not
305
+ // "Insert figure"). The full label stays the accessible name,
306
+ // so nothing is lost for a screen-reader user.
307
+ const fullLabel = meta?.label ?? name;
295
308
  return html `
296
309
  <button
297
310
  type="button"
298
311
  role="menuitem"
299
312
  class="insert-menu-item"
313
+ aria-label=${fullLabel}
300
314
  ?disabled=${!canExecute}
301
315
  @click=${() => this.executeInsertCommand(name, runner)}
302
316
  >
303
317
  ${meta?.icon && materialIcons[meta.icon]
304
318
  ? html `<span class="cmd-icon" aria-hidden="true">${unsafeHTML(materialIcons[meta.icon])}</span>`
305
319
  : nothing}
306
- ${meta?.label ?? name}
320
+ ${meta?.shortLabel ?? fullLabel}
307
321
  </button>
308
322
  `;
309
323
  })}
@@ -96,9 +96,39 @@ export declare class SciFlowSelectionEditorElement extends LitElement {
96
96
  /** Convenience getter mirroring `editor.getCommands()`. */
97
97
  private getCommandRunner;
98
98
  /**
99
- * Render a single attribute field, decoding known encoded values for readability.
99
+ * Whether the bound editor currently accepts edits. Every path in this panel
100
+ * that writes to the document asks first, and fails closed when there is no
101
+ * view to ask.
102
+ *
103
+ * A host can turn editing off at any time
104
+ * (`view.setProps({ editable: () => false })`) and expects the whole surface
105
+ * to go quiet — a side panel that still writes would make a read-only
106
+ * document change under the reader. `view.editable` is the same signal the
107
+ * editor's own key and click handling reads, so the panel cannot drift from
108
+ * the editing surface. The guards sit on the write itself rather than only on
109
+ * the controls: the panel re-renders on selection changes, so editability can
110
+ * flip while a button from the editable render is still on screen.
100
111
  */
112
+ private isEditorEditable;
113
+ /**
114
+ * The panel's own name for a node type in the active locale, or `null` when it
115
+ * has none. Cased for use inside a sentence, so callers that need a heading
116
+ * capitalise it themselves — English writes these names lower case, German
117
+ * capitalises every noun, and only the dictionary knows which.
118
+ */
119
+ private getNodeTypeName;
101
120
  private getNodeTypeLabel;
121
+ /**
122
+ * The node this panel is currently describing, read back from the live
123
+ * document, or `null` when the panel is describing a mark or nothing at all.
124
+ */
125
+ private getSelectedNode;
126
+ /**
127
+ * A table is a `figure` whose first child is a `table` rather than an image,
128
+ * so the content is what tells a reader which one is selected — the node type
129
+ * is `figure` either way.
130
+ */
131
+ private isTableFigure;
102
132
  private renderAttributeField;
103
133
  /**
104
134
  * Convert a raw attribute name like 'text-align' to a human-readable label.
@@ -119,7 +149,18 @@ export declare class SciFlowSelectionEditorElement extends LitElement {
119
149
  private unmountCitationSourceAdapter;
120
150
  private handleDraftAttrChange;
121
151
  /**
122
- * Render the figure sidebar: type, id, alt text.
152
+ * Render the figure sidebar: type, image address, alt text.
153
+ *
154
+ * The image address is editable here because this panel is the only place a
155
+ * reader can change it: clicking an image selects the figure rather than
156
+ * opening a picker, so the address has to be reachable as an ordinary field.
157
+ * It is a plain text input, so a URL, a relative path or a data URI are all
158
+ * accepted; the value is written on `change` like every other draft attribute
159
+ * and committed with Apply.
160
+ *
161
+ * A table is a `figure` too, so the heading and the form's accessible name
162
+ * both come from the selected node rather than from the node type alone —
163
+ * otherwise a selected table would be titled "Figure".
123
164
  */
124
165
  private renderFigureSidebar;
125
166
  /**
@@ -155,8 +196,68 @@ export declare class SciFlowSelectionEditorElement extends LitElement {
155
196
  private setDraftAttr;
156
197
  private applyDraftAttrs;
157
198
  private revertDraftAttrs;
199
+ /**
200
+ * Apply/Revert for pending attribute edits.
201
+ *
202
+ * Withheld when the editor is not editable: `applyDraftAttrs` refuses to write
203
+ * either way — that guard is the barrier — but offering an Apply that cannot
204
+ * act reads as a broken control rather than a read-only document.
205
+ */
158
206
  private renderDraftActions;
207
+ /**
208
+ * Controls that act on the selection itself rather than on its attributes.
209
+ *
210
+ * The delete action sits here, at the top of the panel, deliberately far from
211
+ * the Apply/Update primary at the bottom: a destructive verb never shares a
212
+ * region with the action a user is aiming for. It is a plain `<button>` with
213
+ * visible text, so it is keyboard reachable and its accessible name is the
214
+ * label itself. Removal is undoable through editor history, so it commits
215
+ * straight away rather than opening a confirmation step.
216
+ */
159
217
  private renderSelectionControls;
218
+ /**
219
+ * The node the delete action would remove, or `null` when there is none the
220
+ * panel may act on.
221
+ *
222
+ * Only a `NodeSelection` qualifies: a text range inside a caption or paragraph
223
+ * belongs to the editing surface, not to a panel-level "delete this element"
224
+ * verb. `deleteSelection` is probed with `dispatch` omitted, so the check
225
+ * cannot edit the document.
226
+ *
227
+ * A view that is not editable never yields a target. A host can turn editing
228
+ * off at any time (`view.setProps({ editable: () => false })`) and expects the
229
+ * whole surface to go quiet — a side panel that still removes content would
230
+ * make a read-only document lose it. `view.editable` is the same signal the
231
+ * editor's own key and click handling reads, so the panel cannot drift from
232
+ * the editing surface.
233
+ *
234
+ * Withheld while the panel is pinned, and that is a limitation rather than a
235
+ * design goal. A pinned panel stops following the editor selection and
236
+ * therefore stops re-rendering, while `elementInfo.position` is never remapped
237
+ * through the transaction mapping — so every pinned control already acts on a
238
+ * position that may have drifted, and the attribute fields cope only because
239
+ * they re-check the node type at that position before writing. Removal has no
240
+ * such recheck available: after one deletion the frozen panel would still show
241
+ * the control, now aimed at whatever moved into that position. Making the pin
242
+ * remap its stored position on every document change would fix this for all
243
+ * pinned controls at once; until then the destructive one stays out.
244
+ */
245
+ private getDeletableNode;
246
+ /**
247
+ * Label for the delete action, naming what is about to be removed. The visible
248
+ * text is the button's accessible name, so naming the node here keeps the two
249
+ * identical — no `aria-label` to drift from what is on screen. Anything the
250
+ * panel has no name for falls back to the generic wording.
251
+ */
252
+ private getDeleteLabel;
253
+ /**
254
+ * Remove the selected node through ProseMirror's own deletion command, so the
255
+ * change is a normal transaction and stays undoable and collab-safe. The state
256
+ * is read from the view at call time rather than reused from render, and the
257
+ * editability guard is repeated here: a host can switch the view to read-only
258
+ * after the button was rendered, and the rendered panel would not know.
259
+ */
260
+ private deleteSelectedNode;
160
261
  private togglePinSelection;
161
262
  private handleGenericKeydown;
162
263
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"selection-editor.d.ts","sourceRoot":"","sources":["../../src/lib/selection-editor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"selection-editor.d.ts","sourceRoot":"","sources":["../../src/lib/selection-editor.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,7 @@
13
13
  * - Build reactive UI that stays in sync with editor state
14
14
  */
15
15
  import { __decorate } from "tslib";
16
- import { css, html, LitElement, unsafeCSS } from 'lit';
16
+ import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
17
17
  import { customElement, property, state } from 'lit/decorators.js';
18
18
  import { ref } from 'lit/directives/ref.js';
19
19
  import { SourceField, t, LOCALE_CHANGE_EVENT } from '@sciflow/editor-core';
@@ -22,7 +22,7 @@ import { renderTexToSvg } from '@sciflow/editor-core/features/math';
22
22
  import { Fragment as PMFragment } from 'prosemirror-model';
23
23
  import { EditorState, NodeSelection } from 'prosemirror-state';
24
24
  import { EditorView } from 'prosemirror-view';
25
- import { baseKeymap, toggleMark } from 'prosemirror-commands';
25
+ import { baseKeymap, deleteSelection, toggleMark } from 'prosemirror-commands';
26
26
  import { keymap } from 'prosemirror-keymap';
27
27
  import { getCitationSourceAdapter, } from './citation-source-adapter.js';
28
28
  import { collectDocumentOutline } from './outline.js';
@@ -30,6 +30,14 @@ import { renderNodeFromUiSchema } from './selection-ui-renderer.js';
30
30
  import './selection-widget-host.js';
31
31
  import selectionEditorCss from './selection-editor.css?inline';
32
32
  import { applyThemeStylesToRoot, subscribeToSciFlowTheme } from './theme.js';
33
+ /**
34
+ * Attributes whose value is always text. Anything outside this set that looks
35
+ * like a number is coerced to one before it is written to the node, so a text
36
+ * attribute left out here silently changes type — an image address of `1200`
37
+ * or `3.14` would reach the document as a number and fail schema validation on
38
+ * export. Add a new text attribute here in the same commit that renders it.
39
+ */
40
+ const TEXT_VALUED_ATTRS = new Set(['alt', 'src']);
33
41
  /**
34
42
  * Custom element that displays and edits the selected element's attributes.
35
43
  *
@@ -717,11 +725,32 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
717
725
  return this.resolvedEditor?.commands ?? null;
718
726
  }
719
727
  /**
720
- * Render a single attribute field, decoding known encoded values for readability.
728
+ * Whether the bound editor currently accepts edits. Every path in this panel
729
+ * that writes to the document asks first, and fails closed when there is no
730
+ * view to ask.
731
+ *
732
+ * A host can turn editing off at any time
733
+ * (`view.setProps({ editable: () => false })`) and expects the whole surface
734
+ * to go quiet — a side panel that still writes would make a read-only
735
+ * document change under the reader. `view.editable` is the same signal the
736
+ * editor's own key and click handling reads, so the panel cannot drift from
737
+ * the editing surface. The guards sit on the write itself rather than only on
738
+ * the controls: the panel re-renders on selection changes, so editability can
739
+ * flip while a button from the editable render is still on screen.
721
740
  */
722
- getNodeTypeLabel(type) {
741
+ isEditorEditable() {
742
+ return this.resolvedEditor?.editorView?.editable === true;
743
+ }
744
+ /**
745
+ * The panel's own name for a node type in the active locale, or `null` when it
746
+ * has none. Cased for use inside a sentence, so callers that need a heading
747
+ * capitalise it themselves — English writes these names lower case, German
748
+ * capitalises every noun, and only the dictionary knows which.
749
+ */
750
+ getNodeTypeName(type) {
723
751
  const known = {
724
752
  figure: () => t('selectionEditor.figure'),
753
+ table: () => t('selectionEditor.table'),
725
754
  math: () => t('selectionEditor.math'),
726
755
  citation: () => t('selectionEditor.citation'),
727
756
  anchor: () => t('selectionEditor.hyperlink'),
@@ -729,9 +758,33 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
729
758
  heading: () => t('selectionEditor.heading'),
730
759
  paragraph: () => t('selectionEditor.paragraph'),
731
760
  };
732
- const label = known[type]?.() ?? type.replace(/_/g, ' ');
761
+ return known[type]?.() ?? null;
762
+ }
763
+ getNodeTypeLabel(type) {
764
+ const label = this.getNodeTypeName(type) ?? type.replace(/_/g, ' ');
733
765
  return label.replace(/^\w/, (m) => m.toUpperCase());
734
766
  }
767
+ /**
768
+ * The node this panel is currently describing, read back from the live
769
+ * document, or `null` when the panel is describing a mark or nothing at all.
770
+ */
771
+ getSelectedNode() {
772
+ const view = this.resolvedEditor?.editorView;
773
+ const position = this.elementInfo?.position;
774
+ if (!view || position == null || position < 0 || position >= view.state.doc.content.size) {
775
+ return null;
776
+ }
777
+ return view.state.doc.nodeAt(position);
778
+ }
779
+ /**
780
+ * A table is a `figure` whose first child is a `table` rather than an image,
781
+ * so the content is what tells a reader which one is selected — the node type
782
+ * is `figure` either way.
783
+ */
784
+ isTableFigure(node) {
785
+ const tableType = this.resolvedEditor?.editorView?.state.schema.nodes['table'];
786
+ return !!tableType && node?.firstChild?.type === tableType;
787
+ }
735
788
  renderAttributeField(attrName, attrValue) {
736
789
  const decodedValue = this.decodeSpecialAttribute(attrName, attrValue);
737
790
  const renderedValue = decodedValue === null || decodedValue === undefined ? '' : String(decodedValue);
@@ -852,7 +905,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
852
905
  if (attrName === 'source') {
853
906
  parsedValue = this.encodeSourceField(value);
854
907
  }
855
- else if (attrName === 'alt') {
908
+ else if (TEXT_VALUED_ATTRS.has(attrName)) {
856
909
  parsedValue = value;
857
910
  }
858
911
  else if (value !== '') {
@@ -870,15 +923,50 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
870
923
  this.setDraftAttr(attrName, parsedValue);
871
924
  }
872
925
  /**
873
- * Render the figure sidebar: type, id, alt text.
926
+ * Render the figure sidebar: type, image address, alt text.
927
+ *
928
+ * The image address is editable here because this panel is the only place a
929
+ * reader can change it: clicking an image selects the figure rather than
930
+ * opening a picker, so the address has to be reachable as an ordinary field.
931
+ * It is a plain text input, so a URL, a relative path or a data URI are all
932
+ * accepted; the value is written on `change` like every other draft attribute
933
+ * and committed with Apply.
934
+ *
935
+ * A table is a `figure` too, so the heading and the form's accessible name
936
+ * both come from the selected node rather than from the node type alone —
937
+ * otherwise a selected table would be titled "Figure".
874
938
  */
875
939
  renderFigureSidebar() {
876
940
  const attrs = this.getEffectiveAttrs();
877
941
  const alt = typeof attrs.alt === 'string' ? attrs.alt : '';
942
+ const src = typeof attrs.src === 'string' ? attrs.src : '';
943
+ const isTable = this.isTableFigure(this.getSelectedNode());
878
944
  return html `
879
- <div class="selection-editor-container selection-editor-figure" role="form" aria-label=${t('selectionEditor.editFigure')} @keydown=${this.handleGenericKeydown}>
945
+ <div
946
+ class="selection-editor-container selection-editor-figure"
947
+ role="form"
948
+ aria-label=${isTable ? t('selectionEditor.editTable') : t('selectionEditor.editFigure')}
949
+ @keydown=${this.handleGenericKeydown}
950
+ >
880
951
  ${this.renderSelectionControls()}
881
- <p class="selection-editor-type-header">${this.getNodeTypeLabel(this.elementInfo.type)}</p>
952
+ <p class="selection-editor-type-header">
953
+ ${isTable ? this.getNodeTypeLabel('table') : this.getNodeTypeLabel(this.elementInfo.type)}
954
+ </p>
955
+ ${isTable
956
+ ? nothing
957
+ : html `
958
+ <div class="selection-editor-field">
959
+ <label class="selection-editor-label" for="figure-src">${t('selectionEditor.imageUrl')}</label>
960
+ <input
961
+ type="text"
962
+ id="figure-src"
963
+ class="selection-editor-input"
964
+ .value=${src}
965
+ placeholder=${t('selectionEditor.urlPlaceholder')}
966
+ @change=${(e) => this.handleDraftAttrChange(e, 'src')}
967
+ />
968
+ </div>
969
+ `}
882
970
  <div class="selection-editor-field">
883
971
  <label class="selection-editor-label" for="figure-alt">${t('selectionEditor.altText')}</label>
884
972
  <input
@@ -886,7 +974,9 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
886
974
  id="figure-alt"
887
975
  class="selection-editor-input"
888
976
  .value=${alt}
889
- placeholder=${t('selectionEditor.altTextPlaceholder')}
977
+ placeholder=${isTable
978
+ ? t('selectionEditor.tableAltTextPlaceholder')
979
+ : t('selectionEditor.altTextPlaceholder')}
890
980
  @change=${(e) => this.handleDraftAttrChange(e, 'alt')}
891
981
  />
892
982
  </div>
@@ -1040,6 +1130,9 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1040
1130
  });
1041
1131
  }
1042
1132
  applyMathDraft() {
1133
+ if (!this.isEditorEditable()) {
1134
+ return;
1135
+ }
1043
1136
  if (!this.elementInfo || this.elementInfo.type !== 'math' || this.elementInfo.position === null || !this.mathDraft) {
1044
1137
  return;
1045
1138
  }
@@ -1140,6 +1233,9 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1140
1233
  `;
1141
1234
  }
1142
1235
  applyCitationDraft() {
1236
+ if (!this.isEditorEditable()) {
1237
+ return;
1238
+ }
1143
1239
  if (!this.elementInfo ||
1144
1240
  this.elementInfo.type !== 'citation' ||
1145
1241
  this.elementInfo.position === null ||
@@ -1233,6 +1329,9 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1233
1329
  `;
1234
1330
  }
1235
1331
  applyCrossReferenceDraft() {
1332
+ if (!this.isEditorEditable()) {
1333
+ return;
1334
+ }
1236
1335
  if (!this.elementInfo ||
1237
1336
  this.elementInfo.type !== 'link' ||
1238
1337
  this.elementInfo.position === null ||
@@ -1309,7 +1408,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1309
1408
  }
1310
1409
  applyHyperlinkDraft() {
1311
1410
  const view = this.resolvedEditor?.editorView;
1312
- if (!view || !this.hyperlinkDraft)
1411
+ if (!view || !view.editable || !this.hyperlinkDraft)
1313
1412
  return;
1314
1413
  const { state } = view;
1315
1414
  const anchorMarkType = state.schema.marks['anchor'];
@@ -1345,7 +1444,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1345
1444
  }
1346
1445
  removeHyperlink() {
1347
1446
  const view = this.resolvedEditor?.editorView;
1348
- if (!view)
1447
+ if (!view || !view.editable)
1349
1448
  return;
1350
1449
  const { state } = view;
1351
1450
  const anchorMarkType = state.schema.marks['anchor'];
@@ -1396,6 +1495,9 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1396
1495
  this.draftDirty = Boolean(this.draftAttrs && Object.keys(this.draftAttrs).length > 0);
1397
1496
  }
1398
1497
  applyDraftAttrs() {
1498
+ if (!this.isEditorEditable()) {
1499
+ return;
1500
+ }
1399
1501
  if (!this.draftAttrs || !this.draftDirty || !this.elementInfo || this.elementInfo.position === null) {
1400
1502
  return;
1401
1503
  }
@@ -1417,8 +1519,15 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1417
1519
  revertDraftAttrs() {
1418
1520
  this.resetDraftAttrs();
1419
1521
  }
1522
+ /**
1523
+ * Apply/Revert for pending attribute edits.
1524
+ *
1525
+ * Withheld when the editor is not editable: `applyDraftAttrs` refuses to write
1526
+ * either way — that guard is the barrier — but offering an Apply that cannot
1527
+ * act reads as a broken control rather than a read-only document.
1528
+ */
1420
1529
  renderDraftActions() {
1421
- if (!this.draftDirty) {
1530
+ if (!this.draftDirty || !this.isEditorEditable()) {
1422
1531
  return null;
1423
1532
  }
1424
1533
  return html `
@@ -1441,6 +1550,16 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1441
1550
  </div>
1442
1551
  `;
1443
1552
  }
1553
+ /**
1554
+ * Controls that act on the selection itself rather than on its attributes.
1555
+ *
1556
+ * The delete action sits here, at the top of the panel, deliberately far from
1557
+ * the Apply/Update primary at the bottom: a destructive verb never shares a
1558
+ * region with the action a user is aiming for. It is a plain `<button>` with
1559
+ * visible text, so it is keyboard reachable and its accessible name is the
1560
+ * label itself. Removal is undoable through editor history, so it commits
1561
+ * straight away rather than opening a confirmation step.
1562
+ */
1444
1563
  renderSelectionControls() {
1445
1564
  return html `
1446
1565
  <div class="selection-editor-controls">
@@ -1452,9 +1571,99 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1452
1571
  >
1453
1572
  ${this.pinSelection ? t('selectionEditor.unpinSelection') : t('selectionEditor.pinSelection')}
1454
1573
  </button>
1574
+ ${(() => {
1575
+ const deletable = this.getDeletableNode();
1576
+ return deletable
1577
+ ? html `
1578
+ <button
1579
+ type="button"
1580
+ class="selection-editor-button selection-editor-button--secondary selection-editor-button--danger"
1581
+ @click=${this.deleteSelectedNode}
1582
+ >
1583
+ ${this.getDeleteLabel(deletable)}
1584
+ </button>
1585
+ `
1586
+ : nothing;
1587
+ })()}
1455
1588
  </div>
1456
1589
  `;
1457
1590
  }
1591
+ /**
1592
+ * The node the delete action would remove, or `null` when there is none the
1593
+ * panel may act on.
1594
+ *
1595
+ * Only a `NodeSelection` qualifies: a text range inside a caption or paragraph
1596
+ * belongs to the editing surface, not to a panel-level "delete this element"
1597
+ * verb. `deleteSelection` is probed with `dispatch` omitted, so the check
1598
+ * cannot edit the document.
1599
+ *
1600
+ * A view that is not editable never yields a target. A host can turn editing
1601
+ * off at any time (`view.setProps({ editable: () => false })`) and expects the
1602
+ * whole surface to go quiet — a side panel that still removes content would
1603
+ * make a read-only document lose it. `view.editable` is the same signal the
1604
+ * editor's own key and click handling reads, so the panel cannot drift from
1605
+ * the editing surface.
1606
+ *
1607
+ * Withheld while the panel is pinned, and that is a limitation rather than a
1608
+ * design goal. A pinned panel stops following the editor selection and
1609
+ * therefore stops re-rendering, while `elementInfo.position` is never remapped
1610
+ * through the transaction mapping — so every pinned control already acts on a
1611
+ * position that may have drifted, and the attribute fields cope only because
1612
+ * they re-check the node type at that position before writing. Removal has no
1613
+ * such recheck available: after one deletion the frozen panel would still show
1614
+ * the control, now aimed at whatever moved into that position. Making the pin
1615
+ * remap its stored position on every document change would fix this for all
1616
+ * pinned controls at once; until then the destructive one stays out.
1617
+ */
1618
+ getDeletableNode() {
1619
+ const view = this.resolvedEditor?.editorView;
1620
+ if (!view || this.pinSelection || !view.editable) {
1621
+ return null;
1622
+ }
1623
+ const state = view.state;
1624
+ if (!(state.selection instanceof NodeSelection) || !deleteSelection(state)) {
1625
+ return null;
1626
+ }
1627
+ return state.selection.node;
1628
+ }
1629
+ /**
1630
+ * Label for the delete action, naming what is about to be removed. The visible
1631
+ * text is the button's accessible name, so naming the node here keeps the two
1632
+ * identical — no `aria-label` to drift from what is on screen. Anything the
1633
+ * panel has no name for falls back to the generic wording.
1634
+ */
1635
+ getDeleteLabel(node) {
1636
+ const name = this.isTableFigure(node)
1637
+ ? t('selectionEditor.table')
1638
+ : this.getNodeTypeName(node.type.name);
1639
+ return name
1640
+ ? t('selectionEditor.deleteNamedElement').replace('{element}', name)
1641
+ : t('selectionEditor.deleteElement');
1642
+ }
1643
+ /**
1644
+ * Remove the selected node through ProseMirror's own deletion command, so the
1645
+ * change is a normal transaction and stays undoable and collab-safe. The state
1646
+ * is read from the view at call time rather than reused from render, and the
1647
+ * editability guard is repeated here: a host can switch the view to read-only
1648
+ * after the button was rendered, and the rendered panel would not know.
1649
+ */
1650
+ deleteSelectedNode() {
1651
+ const view = this.resolvedEditor?.editorView;
1652
+ if (!view || !view.editable) {
1653
+ return;
1654
+ }
1655
+ const state = view.state;
1656
+ if (!(state.selection instanceof NodeSelection)) {
1657
+ return;
1658
+ }
1659
+ if (!deleteSelection(state, (tr) => view.dispatch(tr))) {
1660
+ return;
1661
+ }
1662
+ // Focus would otherwise be orphaned on a button that just disappeared.
1663
+ view.focus();
1664
+ const selection = view.state.selection;
1665
+ this.updateElementInfo({ anchor: selection.anchor, head: selection.head });
1666
+ }
1458
1667
  togglePinSelection() {
1459
1668
  this.pinSelection = !this.pinSelection;
1460
1669
  if (!this.pinSelection && this.resolvedEditor?.editorView) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sciflow/editor-start",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "homepage": "https://docs.sciflow.org",
@@ -96,15 +96,15 @@
96
96
  }
97
97
  },
98
98
  "dependencies": {
99
- "@sciflow/schema-core": "0.1.0",
99
+ "@sciflow/schema-core": "0.1.1",
100
100
  "lit": "^3.2.1",
101
101
  "prosemirror-commands": "1.7.1",
102
102
  "prosemirror-keymap": "1.2.3",
103
103
  "tslib": "^2.3.0"
104
104
  },
105
105
  "peerDependencies": {
106
- "@sciflow/editor-core": "0.1.0",
107
- "@sciflow/schema-prosemirror": "0.1.0",
106
+ "@sciflow/editor-core": "0.1.1",
107
+ "@sciflow/schema-prosemirror": "0.1.1",
108
108
  "prosemirror-model": "1.25.8",
109
109
  "prosemirror-state": "1.4.4",
110
110
  "prosemirror-view": "1.41.8"
@@ -114,9 +114,9 @@
114
114
  "@codemirror/lang-xml": "^6.1.0",
115
115
  "@codemirror/theme-one-dark": "^6.1.3",
116
116
  "@material-symbols/svg-400": "^0.44.0",
117
- "@sciflow/editor-core": "0.1.0",
118
- "@sciflow/pandoc-web": "0.1.0",
119
- "@sciflow/schema-prosemirror": "0.1.0",
117
+ "@sciflow/editor-core": "0.1.1",
118
+ "@sciflow/pandoc-web": "0.1.1",
119
+ "@sciflow/schema-prosemirror": "0.1.1",
120
120
  "codemirror": "^6.0.2",
121
121
  "jsdom": "^29.0.2",
122
122
  "prosemirror-model": "1.25.8",
@@ -1,21 +0,0 @@
1
- import { type Feature } from '@sciflow/editor-core';
2
- /**
3
- * The feature set `<sciflow-editor>` activates when no `features` property is
4
- * set. Exported so consumers can *extend* rather than replace the defaults
5
- * (resolves CHANGEREQUEST #1):
6
- *
7
- * ```ts
8
- * import { DEFAULT_EDITOR_FEATURES } from '@sciflow/editor-start';
9
- *
10
- * editor.features = [...DEFAULT_EDITOR_FEATURES, myCustomFeature];
11
- * ```
12
- *
13
- * Lives in its own module (rather than directly in the barrel `index.ts`) so
14
- * `editor-element.ts` can import it without an `index.ts` → `editor-element.ts`
15
- * → `index.ts` import cycle; the barrel re-exports it from here.
16
- *
17
- * `editor-element.ts`'s private `getDefaultFeatures()` delegates to this
18
- * constant — keep the two in sync.
19
- */
20
- export declare const DEFAULT_EDITOR_FEATURES: readonly Feature[];
21
- //# sourceMappingURL=default-features.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"default-features.d.ts","sourceRoot":"","sources":["../../src/lib/default-features.ts"],"names":[],"mappings":"AAAA,OAAO,EAYL,KAAK,OAAO,EACb,MAAM,sBAAsB,CAAC;AAI9B;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,EAAE,SAAS,OAAO,EAarD,CAAC"}