@formicoidea/labre-framework-wardley 0.34.2 → 0.36.0

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.
@@ -98,3 +98,125 @@ export const METHOD = {
98
98
  centerRatio: 0.5, // inner white component radius / R
99
99
  };
100
100
  export const METHOD_LABEL = 'Component';
101
+ /**
102
+ * Porter's forces (composite): a large white circle carrying ONE letter, pushed
103
+ * on from the four cardinal directions by solid red arrows.
104
+ *
105
+ * Twice the market's diameter, at the PO's recette of #210: this is not a link
106
+ * in the value chain drawn a little differently, it is a pressure bearing on the
107
+ * whole map, and it has to read as one at the zoom an architect actually works
108
+ * at. Every other number here is proportional to it.
109
+ *
110
+ * The letter IS the notation — **R** relative competition, **L** struggle for
111
+ * survival, **E** struggle to establish — which is why it is written as the
112
+ * shape's own inner text rather than as a label beside it: a double-click opens
113
+ * the native shape editor on the circle, and typing another letter is the whole
114
+ * of the edit. It is also why this glyph carries no label at all: the three
115
+ * words behind the three letters are the map's vocabulary, not a name the
116
+ * author gives one force.
117
+ */
118
+ export const PORTER_SIZE = 60;
119
+ /**
120
+ * The four arrows, read by `wardleyPorterArrows`: each starts `gap` outside the
121
+ * circle's rim and runs `length` further out, a `width`-wide shaft carrying a
122
+ * head `headLength` long and `headWidth` across.
123
+ *
124
+ * They are drawn as filled POLYGONS rather than as connectors with a triangle
125
+ * endpoint, and the recette of #210 is why: a connector sizes its head off its
126
+ * STROKE WIDTH (`DEFAULT_ARROW_SIZE` × strokeWidth / 2), which is a number about
127
+ * a line rather than about this notation — the head came out longer than the
128
+ * arrow, swallowed the circle, the letter and the double-click, and the glyph
129
+ * rendered as a solid red star. A polygon's head is the number written here and
130
+ * nothing else, at any size.
131
+ */
132
+ export const PORTER_ARROW = {
133
+ gap: 6,
134
+ length: 40,
135
+ /** Shaft width. */
136
+ width: 8,
137
+ headLength: 16,
138
+ headWidth: 24,
139
+ };
140
+ /** The letter, sized to sit inside the circle without touching the rim. */
141
+ export const PORTER_LETTER_FONT_SIZE = 28;
142
+ /** The letter a fresh glyph opens on: relative competition, the commonest read. */
143
+ export const PORTER_DEFAULT_LETTER = 'R';
144
+ /**
145
+ * Accelerators and decelerators — the climate annotations.
146
+ *
147
+ * A fat solid arrow with a flat grey fill and a thick dark border: as close as
148
+ * a native polygon gets to the reference's solid arrow, because this canvas has
149
+ * no gradient fill. The two are the same drawing mirrored — an accelerator
150
+ * points RIGHT, towards the commodity end of the evolution axis, a decelerator
151
+ * points LEFT, back towards genesis — and that direction IS the notation, which
152
+ * is why the outlines are written out below rather than expressed as one shape
153
+ * plus a `rotate`: a rotated element is one the selection, the resize handles
154
+ * and every bounding-box reader then have to de-rotate.
155
+ *
156
+ * Neither is a link in the value chain, so neither carries a component role
157
+ * (see `roles.ts`): they say something about the CLIMATE the map is drawn in.
158
+ */
159
+ export const ACCELERATOR_SIZE = { w: 48, h: 40 };
160
+ /** Flat grey: the arrow is a annotation, and must not out-shout the chain. */
161
+ export const ACCELERATOR_FILL = '#bfbfbf';
162
+ /** A thick dark border — `NODE_STROKE`, like every other artefact's rim. */
163
+ export const ACCELERATOR_STROKE_WIDTH = 2;
164
+ /**
165
+ * The accelerator's outline, normalized to its box, pointing RIGHT.
166
+ *
167
+ * Seven points: the shaft's two left corners, the head's upper barb, the tip,
168
+ * the head's lower barb, and back along the shaft. `0.55` is where the head
169
+ * starts, `0.28 / 0.72` the shaft's own edges.
170
+ */
171
+ export const ACCELERATOR_VERTICES = [
172
+ [0, 0.28],
173
+ [0.55, 0.28],
174
+ [0.55, 0],
175
+ [1, 0.5],
176
+ [0.55, 1],
177
+ [0.55, 0.72],
178
+ [0, 0.72],
179
+ ];
180
+ /** The decelerator: the same outline mirrored `x → 1 − x`, pointing LEFT. */
181
+ export const DECELERATOR_VERTICES = ACCELERATOR_VERTICES.map(([x, y]) => [1 - x, y]);
182
+ export const ACCELERATOR_LABEL = 'Accelerator';
183
+ export const DECELERATOR_LABEL = 'Decelerator';
184
+ /**
185
+ * Areas — the zones of the map.
186
+ *
187
+ * A ring you draw AROUND components to say "all of this is one thing": a
188
+ * business unit, a team's territory, a scope under discussion. It is not a link
189
+ * in the value chain and not an annotation of the climate either, which is why
190
+ * it carries a role with no parent and sits in a category of its own in the
191
+ * toolbox.
192
+ *
193
+ * Two shapes, one kind: a RECT for the common case and a POLYGON for a zone
194
+ * whose outline has to follow the components it groups (its corners are then
195
+ * moved from the shape toolbar's vertex editor). The `shapeType` is what says
196
+ * which, so nothing about an area is a second kind to keep in step.
197
+ *
198
+ * A zone is drawn OVER the map it groups, so the fill is the load-bearing
199
+ * number here: Peace light at ~25 % opacity, which is a wash the components
200
+ * underneath still read through. The border is the saturated Peace of the same
201
+ * swatch, thin like every other artefact's rim — the zone must frame the map,
202
+ * never compete with it.
203
+ */
204
+ export const AREA_RECT_SIZE = { w: 240, h: 160 };
205
+ /** The polygon is born square: its default outline is a regular pentagon. */
206
+ export const AREA_POLYGON_SIZE = { w: 200, h: 200 };
207
+ /** Peace light at ~25 % opacity — an 8-digit hex, exactly as `PIPELINE_FILL`. */
208
+ export const AREA_FILL = '#c6dbfc40';
209
+ /** Peace, the saturated shade of the same swatch. */
210
+ export const AREA_STROKE = '#5b9cf6';
211
+ export const AREA_STROKE_WIDTH = 1;
212
+ /**
213
+ * The word the legend describes an area by.
214
+ *
215
+ * NOT written into the document: an area is created NAMELESS, so the editor
216
+ * that a double-click opens starts on an empty line and the first thing typed
217
+ * is the zone's own name. The other artefacts carry a placeholder because their
218
+ * name is a text element beside them and an empty one would be invisible; an
219
+ * area's name is its own inner text, and a placeholder there would be a word
220
+ * the author has to delete before writing theirs.
221
+ */
222
+ export const AREA_LABEL = 'Area';
@@ -1,10 +1,88 @@
1
+ import { ShapeElementView } from '@formicoidea/labre-core/gfx/shape';
1
2
  import type { WardleyNodeElementModel } from '@formicoidea/labre-core/model';
2
- import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
3
3
  /**
4
4
  * View for a Wardley node. Registering it ensures `gfx.view.get(model)` returns
5
- * a view (required so move / select / connector interactions work). The label
6
- * is a separate native text element, so no custom editing is needed here.
5
+ * a view (required so move / select / connector interactions work).
6
+ *
7
+ * ## It extends `ShapeElementView`, and that is what makes "Edit vertices" work
8
+ *
9
+ * A Wardley node IS a `ShapeElementModel`, and the toolbar's vertex-editing
10
+ * action ends in `if (view instanceof ShapeElementView) view.enterVertexEditingMode()`.
11
+ * While this view extended `GfxElementModelView` directly, that check failed
12
+ * silently: the button was on the row, it was enabled, and clicking it did
13
+ * nothing at all — found by the recette of #213 on an area polygon, whose
14
+ * corners are the whole reason to draw one.
15
+ *
16
+ * Extending the shape view brings TWO wirings, and this framework wants exactly
17
+ * one of each. Both are hooks on the base rather than things to dodge by
18
+ * skipping `onCreated`, because skipping it would drop the wiring that is
19
+ * wanted along with the wiring that is not.
20
+ *
21
+ * ## Double-clicking edits the inner text — on the TWO kinds that have one
22
+ *
23
+ * Every other Wardley artefact wears its name as a SEPARATE canvas text element
24
+ * grouped beside it, so a double-click on the circle has nothing of its own to
25
+ * open: the words live next door and are edited by double-clicking them, which
26
+ * is `TextElementView`'s job and not this one's. Those kinds are deliberately
27
+ * left alone.
28
+ *
29
+ * The `porter` is the first exception, and it is the notation that makes it
30
+ * one. A Porter's-forces glyph carries a LETTER — R relative competition, L
31
+ * struggle for survival, E struggle to establish — and that letter is the
32
+ * shape's own inner text, because it is one character in the middle of the
33
+ * circle rather than a name written alongside it. So the gesture everybody in
34
+ * this editor already makes has to land: `mountShapeTextEditor`, the same call
35
+ * EDGY's and BPMN's node views make on their own inner text.
36
+ *
37
+ * Found by the recette of #210: the view had no handler at all and the node
38
+ * toolbar offers no "add text" action, which left the letter unreachable — a
39
+ * glyph whose whole meaning is one character nobody could change. The four
40
+ * arrows are polygons OUTSIDE the rim (`presets.ts`), so the double-click at
41
+ * the centre reaches this circle rather than a connector's oversized head.
42
+ *
43
+ * The `area` is the second, and for a different reason: a zone's name belongs
44
+ * INSIDE the zone rather than beside it — a label parked outside a boundary
45
+ * would name whatever else happens to be there. It is created with no text at
46
+ * all, so the double-click opens the editor on an empty line and the first
47
+ * thing typed is the zone's name (`mountShapeTextEditor` mints the `Y.Text`).
48
+ *
49
+ * ## …and only on the artefact, never on its wiring
50
+ *
51
+ * The four arrows are `wardleyNode`s too — `kind: 'porter'`, ROLE-LESS, the
52
+ * market's three inner dots exactly — so `kind` alone would hand each of them
53
+ * the same editor. The role is what separates the artefact the author placed
54
+ * from the glyph's own wiring, so it is the second half of the gate, and it is
55
+ * the half that also makes the gate safe to extend: a kind is admitted with the
56
+ * role that proves the element is the artefact rather than a part of one.
57
+ *
58
+ * That pair is also why they are `wardleyNode`s at all. Recette v2 found them
59
+ * as plain shapes, and `ShapeElementView` gives EVERY plain shape a
60
+ * double-click that mounts the inner-text editor: an arrow grew from 24 units
61
+ * high to 44 the moment one was opened, and the deformation survived Escape. An
62
+ * arrow is not a thing you write in, and this view is where that is said.
63
+ *
64
+ * ## Vertices move on the AREA and nowhere else
65
+ *
66
+ * The other wiring. Every other polygon this framework draws has an outline
67
+ * that IS the notation — an accelerator points right and a decelerator points
68
+ * left, and a porter's arrow is the glyph's own wiring — so a handle on one can
69
+ * only turn a statement into a blob. A zone's outline is the opposite: it
70
+ * exists to follow the components it groups. Declining the hook on every other
71
+ * kind skips the overlay, the pointer wiring and the toolbar entry together,
72
+ * which is the same answer the toolbar's own `when` gives.
7
73
  */
8
- export declare class WardleyNodeView extends GfxElementModelView<WardleyNodeElementModel> {
74
+ export declare class WardleyNodeView extends ShapeElementView<WardleyNodeElementModel> {
9
75
  static type: string;
76
+ /**
77
+ * Only the porter CIRCLE and the area. The kind alone is not enough: the
78
+ * porter's four arrows share its kind and are told apart by carrying no role,
79
+ * exactly as the market's inner dots are — hence a table of kind → the role
80
+ * the ARTEFACT carries, and an element that fails to match is either a glyph's
81
+ * own wiring or a kind whose name is the text element beside it. Opening the
82
+ * shape's own editor on those would write a second, invisible name into a
83
+ * field that must stay empty.
84
+ */
85
+ protected editsTextOnDblClick(): boolean;
86
+ /** The zone, and nothing else this framework draws as a polygon. */
87
+ protected editsVerticesOnSelection(): boolean;
10
88
  }
@@ -1,9 +1,113 @@
1
- import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
1
+ import { ShapeElementView } from '@formicoidea/labre-core/gfx/shape';
2
+ import { WARDLEY_ROLE } from '../roles.js';
3
+ /**
4
+ * The kinds whose NAME is the shape's own inner text, each paired with the role
5
+ * an element of that kind carries when it is the artefact itself.
6
+ *
7
+ * A pair and not a list, because `kind` alone admits too much: the porter's
8
+ * four arrows are `wardleyNode`s of kind `porter` carrying no role at all, and
9
+ * a gate on the kind would hand each of them an editor for text they must never
10
+ * have. Anything not in this table is a kind that wears its name as a text
11
+ * element beside it, and its own inner text must stay empty.
12
+ */
13
+ const NAMES_ITSELF = {
14
+ porter: WARDLEY_ROLE.porter,
15
+ area: WARDLEY_ROLE.area,
16
+ };
2
17
  /**
3
18
  * View for a Wardley node. Registering it ensures `gfx.view.get(model)` returns
4
- * a view (required so move / select / connector interactions work). The label
5
- * is a separate native text element, so no custom editing is needed here.
19
+ * a view (required so move / select / connector interactions work).
20
+ *
21
+ * ## It extends `ShapeElementView`, and that is what makes "Edit vertices" work
22
+ *
23
+ * A Wardley node IS a `ShapeElementModel`, and the toolbar's vertex-editing
24
+ * action ends in `if (view instanceof ShapeElementView) view.enterVertexEditingMode()`.
25
+ * While this view extended `GfxElementModelView` directly, that check failed
26
+ * silently: the button was on the row, it was enabled, and clicking it did
27
+ * nothing at all — found by the recette of #213 on an area polygon, whose
28
+ * corners are the whole reason to draw one.
29
+ *
30
+ * Extending the shape view brings TWO wirings, and this framework wants exactly
31
+ * one of each. Both are hooks on the base rather than things to dodge by
32
+ * skipping `onCreated`, because skipping it would drop the wiring that is
33
+ * wanted along with the wiring that is not.
34
+ *
35
+ * ## Double-clicking edits the inner text — on the TWO kinds that have one
36
+ *
37
+ * Every other Wardley artefact wears its name as a SEPARATE canvas text element
38
+ * grouped beside it, so a double-click on the circle has nothing of its own to
39
+ * open: the words live next door and are edited by double-clicking them, which
40
+ * is `TextElementView`'s job and not this one's. Those kinds are deliberately
41
+ * left alone.
42
+ *
43
+ * The `porter` is the first exception, and it is the notation that makes it
44
+ * one. A Porter's-forces glyph carries a LETTER — R relative competition, L
45
+ * struggle for survival, E struggle to establish — and that letter is the
46
+ * shape's own inner text, because it is one character in the middle of the
47
+ * circle rather than a name written alongside it. So the gesture everybody in
48
+ * this editor already makes has to land: `mountShapeTextEditor`, the same call
49
+ * EDGY's and BPMN's node views make on their own inner text.
50
+ *
51
+ * Found by the recette of #210: the view had no handler at all and the node
52
+ * toolbar offers no "add text" action, which left the letter unreachable — a
53
+ * glyph whose whole meaning is one character nobody could change. The four
54
+ * arrows are polygons OUTSIDE the rim (`presets.ts`), so the double-click at
55
+ * the centre reaches this circle rather than a connector's oversized head.
56
+ *
57
+ * The `area` is the second, and for a different reason: a zone's name belongs
58
+ * INSIDE the zone rather than beside it — a label parked outside a boundary
59
+ * would name whatever else happens to be there. It is created with no text at
60
+ * all, so the double-click opens the editor on an empty line and the first
61
+ * thing typed is the zone's name (`mountShapeTextEditor` mints the `Y.Text`).
62
+ *
63
+ * ## …and only on the artefact, never on its wiring
64
+ *
65
+ * The four arrows are `wardleyNode`s too — `kind: 'porter'`, ROLE-LESS, the
66
+ * market's three inner dots exactly — so `kind` alone would hand each of them
67
+ * the same editor. The role is what separates the artefact the author placed
68
+ * from the glyph's own wiring, so it is the second half of the gate, and it is
69
+ * the half that also makes the gate safe to extend: a kind is admitted with the
70
+ * role that proves the element is the artefact rather than a part of one.
71
+ *
72
+ * That pair is also why they are `wardleyNode`s at all. Recette v2 found them
73
+ * as plain shapes, and `ShapeElementView` gives EVERY plain shape a
74
+ * double-click that mounts the inner-text editor: an arrow grew from 24 units
75
+ * high to 44 the moment one was opened, and the deformation survived Escape. An
76
+ * arrow is not a thing you write in, and this view is where that is said.
77
+ *
78
+ * ## Vertices move on the AREA and nowhere else
79
+ *
80
+ * The other wiring. Every other polygon this framework draws has an outline
81
+ * that IS the notation — an accelerator points right and a decelerator points
82
+ * left, and a porter's arrow is the glyph's own wiring — so a handle on one can
83
+ * only turn a statement into a blob. A zone's outline is the opposite: it
84
+ * exists to follow the components it groups. Declining the hook on every other
85
+ * kind skips the overlay, the pointer wiring and the toolbar entry together,
86
+ * which is the same answer the toolbar's own `when` gives.
6
87
  */
7
- export class WardleyNodeView extends GfxElementModelView {
88
+ export class WardleyNodeView extends ShapeElementView {
8
89
  static { this.type = 'wardleyNode'; }
90
+ /**
91
+ * Only the porter CIRCLE and the area. The kind alone is not enough: the
92
+ * porter's four arrows share its kind and are told apart by carrying no role,
93
+ * exactly as the market's inner dots are — hence a table of kind → the role
94
+ * the ARTEFACT carries, and an element that fails to match is either a glyph's
95
+ * own wiring or a kind whose name is the text element beside it. Opening the
96
+ * shape's own editor on those would write a second, invisible name into a
97
+ * field that must stay empty.
98
+ */
99
+ editsTextOnDblClick() {
100
+ // A read-only document is one nobody names anything on. The base view has
101
+ // no such check — a plain shape's editor is harmless there — but this gate
102
+ // is the whole of what a Wardley node offers, so it keeps the guard the
103
+ // hand-written handler carried.
104
+ if (this.std.store.readonly)
105
+ return false;
106
+ const named = NAMES_ITSELF[this.model.kind];
107
+ return named !== undefined && this.model.role === named;
108
+ }
109
+ /** The zone, and nothing else this framework draws as a polygon. */
110
+ editsVerticesOnSelection() {
111
+ return this.model.kind === 'area';
112
+ }
9
113
  }
@@ -0,0 +1,119 @@
1
+ import { InteractivityExtension } from '@formicoidea/labre-core/std/gfx';
2
+ /**
3
+ * Keeps a Porter's-forces glyph saying ONE thing: the letter in the circle and
4
+ * the `wardley:competition` tag on it are two spellings of the same statement.
5
+ *
6
+ * ## Why there are two spellings at all
7
+ *
8
+ * The letter is the NOTATION — an architect reads `L` off the map and knows
9
+ * what it says, and typing another letter is the whole of the edit
10
+ * (`node-view.ts`). The tag is the FACT — the half a rule, a reading or a host
11
+ * report can query, because nothing can ask "which forces does this map name?"
12
+ * of a `Y.Text` holding one character. Dropping either would cost something
13
+ * real, so both exist.
14
+ *
15
+ * ## The invariant: the tag is DERIVED from the letter
16
+ *
17
+ * The two are not symmetric, and the recette of #211 is what made the asymmetry
18
+ * necessary. The letter is what the author writes; the tag is a reading of it,
19
+ * recomputed on EVERY local change of the text and written as a consequence of
20
+ * that change rather than as a step of its own. So `tag = f(text)` holds at
21
+ * every instant, including at every instant an undo or a redo passes through.
22
+ *
23
+ * That is the fix for the defect the recette found. Reconciling only when the
24
+ * editor closed left the two able to disagree: the inline editor writes a
25
+ * select-all-and-retype as several Yjs operations, each its own undo entry,
26
+ * while the tag was one further entry captured after all of them. Undoing then
27
+ * landed on an intermediate text — `RR` — carrying the tag of the text that
28
+ * came after it, and redoing past the tag's own entry re-ran the reconciliation
29
+ * against a text that had already moved on, so the document never returned to
30
+ * where it started.
31
+ *
32
+ * The reading is taken on a MICROTASK after the change, and written UNTRACKED
33
+ * (`Store.withoutTransact`, an origin the undo manager ignores). So the undo
34
+ * stack holds the author's keystrokes and nothing else, and whatever text a
35
+ * step lands on — `E`, the intermediate `RR`, the original `R` — the tag is
36
+ * recomputed from it a tick later. There is no entry to get out of order,
37
+ * because the qualification is not an entry.
38
+ *
39
+ * ## Tag → letter, and why it is the quiet direction
40
+ *
41
+ * A menu pick is the one gesture that starts from the tag, so it — and only it
42
+ * — redraws the letter. Two guards keep that from becoming a second author:
43
+ *
44
+ * - It compares the two SEMANTICALLY (`competitionOfPorterLetter(raw)`), never
45
+ * character by character. A circle reading `e` already says "struggle to
46
+ * establish", so a tag derived from it must not turn round and rewrite it to
47
+ * `E`. Without this the two directions would trade writes on every keystroke.
48
+ * - It never runs while the id is in the EDITING set. That is C4's lesson
49
+ * (`C4TypeLineWatcher`): rewriting the text an author has their caret in
50
+ * throws that caret, and no letter is worth it.
51
+ *
52
+ * The one deliberate asymmetry: a CLEARED tag leaves the circle alone. Removing
53
+ * a value from the menu is a statement about the qualification — "I no longer
54
+ * claim which force this is" — and not about a map somebody is still reading.
55
+ * The tie is re-tied by the next edit of the letter, which is the only gesture
56
+ * that ever asserts one.
57
+ *
58
+ * ## `local`, and always-on
59
+ *
60
+ * Both directions are LOCAL-only, for `EdgyRelationResolver`'s reason: without
61
+ * it every peer on a shared board would derive the same tag the moment a letter
62
+ * synced, giving N−1 of them an entry for a gesture they did not make.
63
+ *
64
+ * Registered by the RENDER half rather than the flag-gated one
65
+ * (`docs/adr/0009`): this creates no element, offers no button and adds no
66
+ * artefact to any map. It keeps an element ALREADY in the document coherent
67
+ * with itself, which a force drawn while the Wardley button was on must stay
68
+ * when the button goes off.
69
+ */
70
+ export declare class WardleyPorterWatcher extends InteractivityExtension {
71
+ static key: string;
72
+ private _selection;
73
+ private _updated;
74
+ private _disposeSurface;
75
+ private _editing;
76
+ private _pending;
77
+ private _scheduled;
78
+ private _mounted;
79
+ mounted(): void;
80
+ unmounted(): void;
81
+ /** The porter this id names, when it is one this peer may rewrite. */
82
+ private _porter;
83
+ /**
84
+ * Queue a re-reading of the letter, for the end of the current task.
85
+ *
86
+ * A MICROTASK, and it is load-bearing rather than cosmetic. `watchText` fires
87
+ * from inside the Yjs transaction that changed the text, and at that instant
88
+ * the element's own `Y.Map` observer may not have run yet — so `element.tags`
89
+ * can still resolve, through `_preserved`, to a nested map the very same
90
+ * transaction has just deleted. A tag written into that map goes nowhere at
91
+ * all: the recette of #211 saw exactly that, as an undo landing on a letter
92
+ * whose force had silently failed to follow it.
93
+ *
94
+ * Deferring past the transaction settles two things at once. The document is
95
+ * consistent when the reading is taken, and the write lands OUTSIDE any
96
+ * tracked transaction — so {@link Store.withoutTransact} really does keep it
97
+ * off the undo stack, and the tag becomes what it claims to be: a pure
98
+ * function of the text, recomputed after every change of it, in either
99
+ * direction of history, and never a step an author has to undo.
100
+ */
101
+ private _scheduleDerive;
102
+ /** The letter as it now stands, written down as the fact it states. */
103
+ private _deriveTagFromText;
104
+ /** The value just picked from the menu, drawn as the letter it stands for. */
105
+ private _drawLetterFromTag;
106
+ }
107
+ /**
108
+ * The ids currently being EDITED, out of a selection update.
109
+ *
110
+ * A pure function because it is the whole of what the watcher needs to know
111
+ * about the selection: a text editor holds an editing selection for as long as
112
+ * it is mounted and drops it when it closes, so this set is "whose caret is in
113
+ * something right now" and nothing else. Total over an empty selection, which
114
+ * is what arrives when an editor closes onto bare canvas.
115
+ */
116
+ export declare function wardleyEditingIds(selections: readonly {
117
+ elements: readonly string[];
118
+ editing?: boolean;
119
+ }[]): Set<string>;