@formicoidea/labre-framework-uml 0.42.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.
- package/LICENSE +384 -0
- package/dist/actions.d.ts +430 -0
- package/dist/actions.js +724 -0
- package/dist/background.d.ts +153 -0
- package/dist/background.js +557 -0
- package/dist/board-hit.d.ts +137 -0
- package/dist/board-hit.js +191 -0
- package/dist/commands-manifest.d.ts +19 -0
- package/dist/commands-manifest.js +571 -0
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +983 -0
- package/dist/component.d.ts +521 -0
- package/dist/component.js +708 -0
- package/dist/consts.d.ts +365 -0
- package/dist/consts.js +463 -0
- package/dist/descriptor.d.ts +12 -0
- package/dist/descriptor.js +10 -0
- package/dist/drawio-decode.d.ts +32 -0
- package/dist/drawio-decode.js +96 -0
- package/dist/drawio-import.d.ts +133 -0
- package/dist/drawio-import.js +885 -0
- package/dist/edge-morph.d.ts +154 -0
- package/dist/edge-morph.js +153 -0
- package/dist/edge-styles.d.ts +78 -0
- package/dist/edge-styles.js +210 -0
- package/dist/effects.d.ts +9 -0
- package/dist/effects.js +6 -0
- package/dist/element-renderer.d.ts +64 -0
- package/dist/element-renderer.js +114 -0
- package/dist/element-view.d.ts +234 -0
- package/dist/element-view.js +430 -0
- package/dist/export.d.ts +61 -0
- package/dist/export.js +41 -0
- package/dist/filename.d.ts +17 -0
- package/dist/filename.js +26 -0
- package/dist/frame-tag.d.ts +70 -0
- package/dist/frame-tag.js +78 -0
- package/dist/grammar.d.ts +695 -0
- package/dist/grammar.js +1436 -0
- package/dist/import.d.ts +306 -0
- package/dist/import.js +1270 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +67 -0
- package/dist/interchange.d.ts +125 -0
- package/dist/interchange.js +372 -0
- package/dist/keywords.d.ts +204 -0
- package/dist/keywords.js +361 -0
- package/dist/kinds.d.ts +73 -0
- package/dist/kinds.js +91 -0
- package/dist/legend.d.ts +30 -0
- package/dist/legend.js +255 -0
- package/dist/model.d.ts +862 -0
- package/dist/model.js +1316 -0
- package/dist/morph.d.ts +178 -0
- package/dist/morph.js +496 -0
- package/dist/node/act-glyphs.d.ts +30 -0
- package/dist/node/act-glyphs.js +142 -0
- package/dist/node/compartment-watcher.d.ts +107 -0
- package/dist/node/compartment-watcher.js +163 -0
- package/dist/node/fit.d.ts +85 -0
- package/dist/node/fit.js +129 -0
- package/dist/node/node-renderer.d.ts +49 -0
- package/dist/node/node-renderer.js +611 -0
- package/dist/node/node-view.d.ts +51 -0
- package/dist/node/node-view.js +89 -0
- package/dist/node/paint.d.ts +84 -0
- package/dist/node/paint.js +143 -0
- package/dist/node/sd-glyphs.d.ts +36 -0
- package/dist/node/sd-glyphs.js +79 -0
- package/dist/node/stm-glyphs.d.ts +22 -0
- package/dist/node/stm-glyphs.js +111 -0
- package/dist/node/tier-metrics.d.ts +38 -0
- package/dist/node/tier-metrics.js +45 -0
- package/dist/operators.d.ts +45 -0
- package/dist/operators.js +75 -0
- package/dist/plantuml-import.d.ts +113 -0
- package/dist/plantuml-import.js +1723 -0
- package/dist/plantuml.d.ts +94 -0
- package/dist/plantuml.js +985 -0
- package/dist/presets.d.ts +149 -0
- package/dist/presets.js +302 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +358 -0
- package/dist/reading.d.ts +160 -0
- package/dist/reading.js +648 -0
- package/dist/roles.d.ts +257 -0
- package/dist/roles.js +997 -0
- package/dist/rules.d.ts +267 -0
- package/dist/rules.js +3090 -0
- package/dist/templates.d.ts +2 -0
- package/dist/templates.js +197 -0
- package/dist/toolbar/config.d.ts +228 -0
- package/dist/toolbar/config.js +833 -0
- package/dist/toolbar/icons.d.ts +440 -0
- package/dist/toolbar/icons.js +711 -0
- package/dist/toolbar/senior-tool.d.ts +11 -0
- package/dist/toolbar/senior-tool.js +21 -0
- package/dist/toolbar/uml-menu.d.ts +17 -0
- package/dist/toolbar/uml-menu.js +20 -0
- package/dist/toolbar/uml-senior-button.d.ts +18 -0
- package/dist/toolbar/uml-senior-button.js +22 -0
- package/dist/translations.d.ts +27 -0
- package/dist/translations.js +117 -0
- package/dist/view.d.ts +33 -0
- package/dist/view.js +236 -0
- package/dist/xmi-import.d.ts +174 -0
- package/dist/xmi-import.js +2268 -0
- package/dist/xmi.d.ts +61 -0
- package/dist/xmi.js +1860 -0
- package/dist/xml-reader.d.ts +157 -0
- package/dist/xml-reader.js +430 -0
- package/dist/xml.d.ts +72 -0
- package/dist/xml.js +96 -0
- package/package.json +38 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { shape as shapeRenderer } from '@formicoidea/labre-core/gfx/shape';
|
|
3
|
+
import { DefaultTheme, } from '@formicoidea/labre-core/model';
|
|
4
|
+
import { UML_ACTOR_FIGURE, UML_CUBE_DEPTH, UML_PACKAGE_TAB, umlCompartmentBoxes, umlComponentSiblings, umlStackHeight, } from '../component.js';
|
|
5
|
+
import { isActGlyphKind, paintActGlyph } from './act-glyphs.js';
|
|
6
|
+
import { line, solidRect, TAU } from './paint.js';
|
|
7
|
+
import { isSdGlyphKind, paintSdGlyph } from './sd-glyphs.js';
|
|
8
|
+
import { isStmGlyphKind, paintStmGlyph } from './stm-glyphs.js';
|
|
9
|
+
/**
|
|
10
|
+
* Renderer for a UML node — the glyph layer, and nothing else.
|
|
11
|
+
*
|
|
12
|
+
* ## What the native shape does, and what this file does
|
|
13
|
+
*
|
|
14
|
+
* The native shape renderer runs FIRST and owns the fill, the stroke and the
|
|
15
|
+
* theme. This file then paints, in the element-local frame, only what a
|
|
16
|
+
* rectangle cannot be: the compartment separators of §11.4.4, the underline of
|
|
17
|
+
* an instance's name (§9.8.4), the package's tab (§12.2.4), the note's folded
|
|
18
|
+
* corner (Annex A), the actor's stick figure (§18.1.4), the component's
|
|
19
|
+
* two-tabbed icon (§11.6.4), the artifact's document icon (§19.3.4), the port's
|
|
20
|
+
* square (§11.3.4), the ball and socket of §10.4.4 and the 3-D cube of §19.4.4.
|
|
21
|
+
*
|
|
22
|
+
* The behaviour families of phase 2 add fourteen more marks, and they live in
|
|
23
|
+
* two modules of their own — `act-glyphs.ts` for the control nodes of §15.3.4
|
|
24
|
+
* and the signal shapes of §16.3.4 / §16.10.4, `stm-glyphs.ts` for the final
|
|
25
|
+
* state and the pseudostates of §14.2.4. The BRANCH stays here, because which
|
|
26
|
+
* kind gets which picture is this file's subject and the `never` at the end of
|
|
27
|
+
* `paintGlyph` is what keeps it total; the drawing moved out because a
|
|
28
|
+
* thousand-line renderer is a file nobody reads the middle of.
|
|
29
|
+
*
|
|
30
|
+
* Five kinds need nothing at all. A `use-case` IS the native ellipse (§18.1.4),
|
|
31
|
+
* an `action` and a `state`'s body the native ROUNDED rect (§15.3.4, §14.2.4),
|
|
32
|
+
* a `decision` and a `choice` the native diamond, an `object-node` the plain
|
|
33
|
+
* native rect (§15.4.4) — and drawing anything over any of them would be
|
|
34
|
+
* inventing a notation. An `object` is a class box with a split and a rule under
|
|
35
|
+
* its name, which is the first branch below rather than a picture; so are a
|
|
36
|
+
* `component` and an `artifact`, each with a small icon dropped in the corner of
|
|
37
|
+
* the box the shape layer has already drawn.
|
|
38
|
+
*
|
|
39
|
+
* ## Every offset is READ, never restated
|
|
40
|
+
*
|
|
41
|
+
* The separator positions come from `umlCompartmentBoxes` — the same pure
|
|
42
|
+
* function the creation site places the text tiers with — so a rule is drawn
|
|
43
|
+
* exactly between two compartments rather than near where they were last time
|
|
44
|
+
* somebody looked. The actor's figure is bounded by the top of its own label
|
|
45
|
+
* box, from the same call, so the words can never land on the legs.
|
|
46
|
+
*
|
|
47
|
+
* …and once a component's tiers are ON the canvas, they are the better source
|
|
48
|
+
* still: `umlNodeCompartments` reads the separators off the body tier's own box
|
|
49
|
+
* when the group can be reached, so a classifier `UmlCompartmentWatcher` has
|
|
50
|
+
* grown for a five-line attributes compartment is ruled where its compartments
|
|
51
|
+
* now are. The default stack is the answer for a shape with no group behind it,
|
|
52
|
+
* which is every one of them at creation time.
|
|
53
|
+
*
|
|
54
|
+
* ## Colours come off the MODEL
|
|
55
|
+
*
|
|
56
|
+
* Both fill and stroke are read from the element, never from a table: a node's
|
|
57
|
+
* colours are editable from the shape toolbar like any other shape's, and a
|
|
58
|
+
* glyph that painted the pack's own ink would silently ignore the user's choice.
|
|
59
|
+
* `presets.ts` is what SEEDS them.
|
|
60
|
+
*
|
|
61
|
+
* The glyph-bodied kinds — `package`, `note`, `actor`, `port`, the two interface
|
|
62
|
+
* glyphs and the three cubes — are created unfilled and unstroked
|
|
63
|
+
* (`presets.ts`), so the native rect paints nothing and the glyph IS the body:
|
|
64
|
+
* it fills with the element's `fillColor` and outlines with its `strokeColor`,
|
|
65
|
+
* which is what keeps them recolourable from the same toolbar as every other
|
|
66
|
+
* shape.
|
|
67
|
+
*/
|
|
68
|
+
/* ── The glyph geometry, as fractions of the node box ──────────────────── */
|
|
69
|
+
/**
|
|
70
|
+
* How wide the package's tab is (§12.2.4), as a fraction of the box.
|
|
71
|
+
*
|
|
72
|
+
* Only the WIDTH: the tab's height is `UML_PACKAGE_TAB`, owned by
|
|
73
|
+
* `component.ts` because that is where the name compartment is placed under it,
|
|
74
|
+
* and two files each picking their own number is how a label comes to sit on a
|
|
75
|
+
* fold. Wide enough to read as a folder tab and no wider — a tab that ran most
|
|
76
|
+
* of the width would read as a compartment.
|
|
77
|
+
*/
|
|
78
|
+
const PACKAGE_TAB_WIDTH = 0.42;
|
|
79
|
+
/** The note's folded corner (Annex A), as a fraction of the SHORTER side. */
|
|
80
|
+
const NOTE_FOLD = 0.22;
|
|
81
|
+
/**
|
|
82
|
+
* The actor's stick figure (§18.1.4), in fractions of the figure box — which is
|
|
83
|
+
* the element box down to the top of its label, never the element box itself.
|
|
84
|
+
*/
|
|
85
|
+
const ACTOR = {
|
|
86
|
+
/** Head radius, against the figure's width and against its height. */
|
|
87
|
+
headOfWidth: 0.24,
|
|
88
|
+
headOfHeight: 0.18,
|
|
89
|
+
/** Where the spine ends, as a fraction of the figure's height. */
|
|
90
|
+
hipY: 0.66,
|
|
91
|
+
/** The arms: their height, and how far out they reach. */
|
|
92
|
+
armY: 0.46,
|
|
93
|
+
armX: 0.06,
|
|
94
|
+
/** Where the feet land. */
|
|
95
|
+
footY: 0.98,
|
|
96
|
+
footX: 0.12,
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* The component's own icon (§11.6.4) — the little rectangle with two tabs
|
|
100
|
+
* sticking out of its left edge, dropped in the top-right corner of the box.
|
|
101
|
+
*
|
|
102
|
+
* Fractions of the node box, like every other glyph metric here, so a component
|
|
103
|
+
* dragged to any size carries an icon in proportion to it. The icon is what
|
|
104
|
+
* MAKES the box a component: §11.6.4 offers the icon or the `«component»`
|
|
105
|
+
* keyword, and this pack draws the icon, which is why the name seed writes no
|
|
106
|
+
* keyword (`keywords.ts`).
|
|
107
|
+
*/
|
|
108
|
+
const COMPONENT_ICON = {
|
|
109
|
+
/** The icon body, against the box's width and height. */
|
|
110
|
+
width: 0.16,
|
|
111
|
+
height: 0.2,
|
|
112
|
+
/** Its inset from the top-right corner, against the shorter side. */
|
|
113
|
+
margin: 0.06,
|
|
114
|
+
/** The two tabs, against the icon body: how far they protrude, how tall. */
|
|
115
|
+
tabWidth: 0.4,
|
|
116
|
+
tabHeight: 0.26,
|
|
117
|
+
/** Where each tab's top edge sits, against the icon body's height. */
|
|
118
|
+
tabTop: [0.16, 0.58],
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* The artifact's document icon (§19.3.4) — a small sheet of paper with its
|
|
122
|
+
* top-right corner turned down, in the same corner as the component's.
|
|
123
|
+
*
|
|
124
|
+
* Narrower and taller than the component's icon because it is a PAGE, and a page
|
|
125
|
+
* read as a square would read as a box.
|
|
126
|
+
*/
|
|
127
|
+
const ARTIFACT_ICON = {
|
|
128
|
+
width: 0.1,
|
|
129
|
+
height: 0.19,
|
|
130
|
+
margin: 0.06,
|
|
131
|
+
/** The turned-down corner, against the icon's shorter side. */
|
|
132
|
+
fold: 0.36,
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* The ball and the socket (§10.4.4), as fractions of the glyph's box.
|
|
136
|
+
*
|
|
137
|
+
* One radius from whichever dimension is the tighter, so the ball stays ROUND at
|
|
138
|
+
* any aspect ratio the element is dragged to — the same rule the actor's head
|
|
139
|
+
* follows, and for the same reason. What is left under the circle is the STUB:
|
|
140
|
+
* the short line that, on a real diagram, runs to the component's border.
|
|
141
|
+
*/
|
|
142
|
+
const INTERFACE_GLYPH = {
|
|
143
|
+
radiusOfWidth: 0.3,
|
|
144
|
+
radiusOfHeight: 0.34,
|
|
145
|
+
};
|
|
146
|
+
/* ── The glyphs ────────────────────────────────────────────────────────── */
|
|
147
|
+
/**
|
|
148
|
+
* Paint one kind, in the element-local frame, with `fillStyle` / `strokeStyle` /
|
|
149
|
+
* `lineWidth` already set from the model.
|
|
150
|
+
*
|
|
151
|
+
* @param inset half the stroke width — a body is drawn inside it, so the outline
|
|
152
|
+
* lands within the element's bounds rather than straddling them.
|
|
153
|
+
*/
|
|
154
|
+
function paintGlyph(kind, ctx, w, h, inset, boxes) {
|
|
155
|
+
const x0 = inset;
|
|
156
|
+
const y0 = inset;
|
|
157
|
+
const x1 = w - inset;
|
|
158
|
+
const y1 = h - inset;
|
|
159
|
+
const bw = x1 - x0;
|
|
160
|
+
const bh = y1 - y0;
|
|
161
|
+
if (!(bw > 0) || !(bh > 0))
|
|
162
|
+
return;
|
|
163
|
+
// ── The compartmented kinds: one rule per split ──────────────────────
|
|
164
|
+
if (kind === 'class' ||
|
|
165
|
+
kind === 'interface' ||
|
|
166
|
+
kind === 'enumeration' ||
|
|
167
|
+
kind === 'object' ||
|
|
168
|
+
kind === 'component' ||
|
|
169
|
+
kind === 'artifact' ||
|
|
170
|
+
// §14.2.4 rules a state off between its name and its internal activities —
|
|
171
|
+
// the object's layout exactly, drawn on the native ROUNDED rect the preset
|
|
172
|
+
// gives it rather than on a square one.
|
|
173
|
+
kind === 'state') {
|
|
174
|
+
// Full width, edge to edge: §11.4.4 draws a compartment line right across
|
|
175
|
+
// the classifier, unlike the inset the text tiers sit within.
|
|
176
|
+
for (const split of boxes.splits) {
|
|
177
|
+
if (split <= y0 || split >= y1)
|
|
178
|
+
continue;
|
|
179
|
+
line(ctx, x0, split, x1, split);
|
|
180
|
+
}
|
|
181
|
+
// An instance's name is UNDERLINED (§9.8.4) — the one mark that tells a
|
|
182
|
+
// reader an object from the class it is an instance of. Drawn under the NAME
|
|
183
|
+
// box and only as wide as it, so it reads as a rule under the words rather
|
|
184
|
+
// than as a fifth compartment line.
|
|
185
|
+
if (kind === 'object') {
|
|
186
|
+
const { name } = boxes;
|
|
187
|
+
const under = name.y + name.h;
|
|
188
|
+
if (under > y0 && under < y1 && name.w > 0) {
|
|
189
|
+
line(ctx, name.x, under, name.x + name.w, under);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// The two kinds whose box is told from a class's by a picture in its
|
|
193
|
+
// corner, drawn over the body the shape layer has already filled.
|
|
194
|
+
if (kind === 'component')
|
|
195
|
+
paintComponentIcon(ctx, x1, y0, bw, bh);
|
|
196
|
+
if (kind === 'artifact')
|
|
197
|
+
paintArtifactIcon(ctx, x1, y0, bw, bh);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// ── The package: a tab, and the body under it (§12.2.4) ──────────────
|
|
201
|
+
if (kind === 'package') {
|
|
202
|
+
const tabW = bw * PACKAGE_TAB_WIDTH;
|
|
203
|
+
// Against the ELEMENT box, not the stroke-inset one: `component.ts` places
|
|
204
|
+
// the name compartment under `h * UML_PACKAGE_TAB`, and the fold has to be
|
|
205
|
+
// the line that number names.
|
|
206
|
+
const tabH = Math.max(0, Math.min(h * UML_PACKAGE_TAB - y0, bh));
|
|
207
|
+
// The tab first, so the body's top edge — drawn over it — closes the join
|
|
208
|
+
// on the right of the tab and nowhere else, which is the folder silhouette.
|
|
209
|
+
ctx.beginPath();
|
|
210
|
+
ctx.moveTo(x0, y0 + tabH);
|
|
211
|
+
ctx.lineTo(x0, y0);
|
|
212
|
+
ctx.lineTo(x0 + tabW, y0);
|
|
213
|
+
ctx.lineTo(x0 + tabW, y0 + tabH);
|
|
214
|
+
ctx.closePath();
|
|
215
|
+
ctx.fill();
|
|
216
|
+
ctx.stroke();
|
|
217
|
+
ctx.beginPath();
|
|
218
|
+
ctx.moveTo(x0, y0 + tabH);
|
|
219
|
+
ctx.lineTo(x1, y0 + tabH);
|
|
220
|
+
ctx.lineTo(x1, y1);
|
|
221
|
+
ctx.lineTo(x0, y1);
|
|
222
|
+
ctx.closePath();
|
|
223
|
+
ctx.fill();
|
|
224
|
+
ctx.stroke();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// ── The note: a rectangle with its top-right corner turned down ──────
|
|
228
|
+
if (kind === 'note') {
|
|
229
|
+
const fold = Math.min(bw, bh) * NOTE_FOLD;
|
|
230
|
+
ctx.beginPath();
|
|
231
|
+
ctx.moveTo(x0, y0);
|
|
232
|
+
ctx.lineTo(x1 - fold, y0);
|
|
233
|
+
ctx.lineTo(x1, y0 + fold);
|
|
234
|
+
ctx.lineTo(x1, y1);
|
|
235
|
+
ctx.lineTo(x0, y1);
|
|
236
|
+
ctx.closePath();
|
|
237
|
+
ctx.fill();
|
|
238
|
+
ctx.stroke();
|
|
239
|
+
// The fold itself: the two edges of the turned-down triangle. Drawn as
|
|
240
|
+
// lines and not as a filled flap, which is how Annex A draws it — a shaded
|
|
241
|
+
// flap would claim a light source the notation has none of.
|
|
242
|
+
ctx.beginPath();
|
|
243
|
+
ctx.moveTo(x1 - fold, y0);
|
|
244
|
+
ctx.lineTo(x1 - fold, y0 + fold);
|
|
245
|
+
ctx.lineTo(x1, y0 + fold);
|
|
246
|
+
ctx.stroke();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// ── The actor: a stick figure over its own name (§18.1.4) ────────────
|
|
250
|
+
if (kind === 'actor') {
|
|
251
|
+
// The figure stops where the label starts: `component.ts` owns that line
|
|
252
|
+
// (`UML_ACTOR_FIGURE`) and places the name element below it, so the words
|
|
253
|
+
// can never land on the legs.
|
|
254
|
+
const figureH = Math.max(0, Math.min(h * UML_ACTOR_FIGURE - y0, bh));
|
|
255
|
+
if (!(figureH > 0))
|
|
256
|
+
return;
|
|
257
|
+
const cx = w / 2;
|
|
258
|
+
// One radius from whichever dimension is the tighter, so the head stays
|
|
259
|
+
// CIRCULAR at any aspect ratio the author drags the element to.
|
|
260
|
+
const headR = Math.max(0, Math.min(bw * ACTOR.headOfWidth, figureH * ACTOR.headOfHeight));
|
|
261
|
+
if (headR > 0) {
|
|
262
|
+
ctx.beginPath();
|
|
263
|
+
ctx.arc(cx, y0 + headR, headR, 0, TAU);
|
|
264
|
+
ctx.fill();
|
|
265
|
+
ctx.stroke();
|
|
266
|
+
}
|
|
267
|
+
const hipY = y0 + figureH * ACTOR.hipY;
|
|
268
|
+
// The spine, from under the head down to the hips.
|
|
269
|
+
line(ctx, cx, y0 + headR * 2, cx, hipY);
|
|
270
|
+
// The arms, straight across.
|
|
271
|
+
const armY = y0 + figureH * ACTOR.armY;
|
|
272
|
+
line(ctx, x0 + bw * ACTOR.armX, armY, x1 - bw * ACTOR.armX, armY);
|
|
273
|
+
// …and the two legs.
|
|
274
|
+
const footY = y0 + figureH * ACTOR.footY;
|
|
275
|
+
line(ctx, cx, hipY, x0 + bw * ACTOR.footX, footY);
|
|
276
|
+
line(ctx, cx, hipY, x1 - bw * ACTOR.footX, footY);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// ── The port: a small filled square (§11.3.4) ────────────────────────
|
|
280
|
+
if (kind === 'port') {
|
|
281
|
+
// A SQUARE, from the shorter side and centred: a port dragged into a
|
|
282
|
+
// rectangle is still a port, and §11.3.4 draws one square. The whole body,
|
|
283
|
+
// because the port is created unstroked and unfilled — the glyph is it.
|
|
284
|
+
const side = Math.min(bw, bh);
|
|
285
|
+
solidRect(ctx, x0 + (bw - side) / 2, y0 + (bh - side) / 2, side, side);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
// ── The two interface glyphs: a ball, or a socket, on a stub ─────────
|
|
289
|
+
if (kind === 'provided-interface' || kind === 'required-interface') {
|
|
290
|
+
const radius = Math.max(0, Math.min(bw * INTERFACE_GLYPH.radiusOfWidth, bh * INTERFACE_GLYPH.radiusOfHeight));
|
|
291
|
+
if (!(radius > 0))
|
|
292
|
+
return;
|
|
293
|
+
const cx = w / 2;
|
|
294
|
+
const cy = y0 + radius;
|
|
295
|
+
if (kind === 'provided-interface') {
|
|
296
|
+
// The lollipop: a CLOSED circle, filled, saying the component offers this
|
|
297
|
+
// interface (§10.4.4).
|
|
298
|
+
ctx.beginPath();
|
|
299
|
+
ctx.arc(cx, cy, radius, 0, TAU);
|
|
300
|
+
ctx.fill();
|
|
301
|
+
ctx.stroke();
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// The socket: the BOTTOM half of the circle, a cup opening UPWARD — away
|
|
305
|
+
// from the component the stub below runs to, so a ball can nest in it
|
|
306
|
+
// (§10.4.4). Its deepest point is (cx, cy + radius), exactly where the
|
|
307
|
+
// stub starts, so the line meets the back of the cup and not a horn.
|
|
308
|
+
// Stroked and never filled: it is an arc, and a filled half-disc would
|
|
309
|
+
// read as a ball cut in two.
|
|
310
|
+
ctx.beginPath();
|
|
311
|
+
ctx.arc(cx, cy, radius, 0, Math.PI);
|
|
312
|
+
ctx.stroke();
|
|
313
|
+
}
|
|
314
|
+
// The stub: the short line that, on a real diagram, runs from the glyph to
|
|
315
|
+
// the border of the component it belongs to. Drawn straight DOWN, which is
|
|
316
|
+
// the one direction that needs no knowledge of where that component is.
|
|
317
|
+
if (cy + radius < y1)
|
|
318
|
+
line(ctx, cx, cy + radius, cx, y1);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// ── The three deployment targets: one 3-D cube (§19.4.4) ─────────────
|
|
322
|
+
if (kind === 'node' ||
|
|
323
|
+
kind === 'device' ||
|
|
324
|
+
kind === 'execution-environment') {
|
|
325
|
+
// Against the ELEMENT box, not the stroke-inset one, and clamped: the front
|
|
326
|
+
// face has to be the rectangle `component.ts` writes the name inside, and
|
|
327
|
+
// two files each deriving their own depth is how a name ends up on a roof.
|
|
328
|
+
const depth = Math.max(0, Math.min(Math.min(w, h) * UML_CUBE_DEPTH, Math.min(bw, bh)));
|
|
329
|
+
const backX = x1 - depth;
|
|
330
|
+
const backY = y0 + depth;
|
|
331
|
+
// The top and the right first, the FRONT last: the front face is opaque, so
|
|
332
|
+
// painting it over the other two closes every join that should not be seen
|
|
333
|
+
// and leaves the three edges that make the solid read as a solid.
|
|
334
|
+
if (depth > 0) {
|
|
335
|
+
ctx.beginPath();
|
|
336
|
+
ctx.moveTo(x0, backY);
|
|
337
|
+
ctx.lineTo(x0 + depth, y0);
|
|
338
|
+
ctx.lineTo(x1, y0);
|
|
339
|
+
ctx.lineTo(backX, backY);
|
|
340
|
+
ctx.closePath();
|
|
341
|
+
ctx.fill();
|
|
342
|
+
ctx.stroke();
|
|
343
|
+
ctx.beginPath();
|
|
344
|
+
ctx.moveTo(backX, backY);
|
|
345
|
+
ctx.lineTo(x1, y0);
|
|
346
|
+
ctx.lineTo(x1, y1 - depth);
|
|
347
|
+
ctx.lineTo(backX, y1);
|
|
348
|
+
ctx.closePath();
|
|
349
|
+
ctx.fill();
|
|
350
|
+
ctx.stroke();
|
|
351
|
+
}
|
|
352
|
+
solidRect(ctx, x0, backY, backX - x0, y1 - backY);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
// ── The kinds the SHAPE LAYER draws whole, with nothing painted over ──
|
|
356
|
+
//
|
|
357
|
+
// A use case is the native ellipse of §18.1.4; an `action` and the body of a
|
|
358
|
+
// `state` are the native ROUNDED rect of §15.3.4 and §14.2.4 (`radius` on the
|
|
359
|
+
// preset, not a glyph); a `decision` and a `choice` are the native diamond;
|
|
360
|
+
// an `object-node` is the plain native rect of §15.4.4. Drawing anything over
|
|
361
|
+
// any of them would be inventing a notation.
|
|
362
|
+
//
|
|
363
|
+
// `state` is absent from this list because it is handled ABOVE, with the
|
|
364
|
+
// compartmented kinds: the rounded body is the shape layer's, the rule under
|
|
365
|
+
// the name is this file's.
|
|
366
|
+
if (kind === 'use-case' ||
|
|
367
|
+
kind === 'action' ||
|
|
368
|
+
kind === 'object-node' ||
|
|
369
|
+
kind === 'decision' ||
|
|
370
|
+
kind === 'choice' ||
|
|
371
|
+
// …and, since phase 3, §17.2.4's ExecutionSpecification: a thin FILLED
|
|
372
|
+
// rectangle on a lifeline's spine, which is a native rect and nothing more.
|
|
373
|
+
kind === 'execution') {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
// ── The behaviour marks, by family ───────────────────────────────────
|
|
377
|
+
//
|
|
378
|
+
// Delegated rather than inlined: the two families between them are fourteen
|
|
379
|
+
// pictures, and the branch a reader needs to follow is WHICH KIND GETS WHICH,
|
|
380
|
+
// not how a bullseye is struck. Each module closes its own switch with a
|
|
381
|
+
// `never`, so the delegation costs none of the exhaustiveness the last branch
|
|
382
|
+
// of this function exists for.
|
|
383
|
+
const box = { x0, y0, x1, y1, bw, bh, w, h };
|
|
384
|
+
if (isActGlyphKind(kind)) {
|
|
385
|
+
paintActGlyph(kind, ctx, box);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (isStmGlyphKind(kind)) {
|
|
389
|
+
paintStmGlyph(kind, ctx, box);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// Phase 3's two: §17.3.4's lifeline — the one glyph in the pack that is
|
|
393
|
+
// BIGGER than the element it belongs to — and §17.2.4's destruction X.
|
|
394
|
+
if (isSdGlyphKind(kind)) {
|
|
395
|
+
paintSdGlyph(kind, ctx, box);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Every kind is drawn above, and this is what keeps that true: `kind` is
|
|
400
|
+
* narrowed to `never` here only if the branches are exhaustive over
|
|
401
|
+
* {@link UmlNodeKind}, so a kind added to the model's union without a picture
|
|
402
|
+
* of its own stops the build.
|
|
403
|
+
*
|
|
404
|
+
* Which is the whole point of closing the last branch rather than letting it
|
|
405
|
+
* fall through. A renderer that silently paints a package tab on somebody's
|
|
406
|
+
* new artefact is worse than one that paints nothing: the first is a wrong
|
|
407
|
+
* picture nobody is told about, the second is a missing one everybody sees.
|
|
408
|
+
*/
|
|
409
|
+
const unhandled = kind;
|
|
410
|
+
void unhandled;
|
|
411
|
+
}
|
|
412
|
+
/* ── The two corner icons ──────────────────────────────────────────────── */
|
|
413
|
+
/**
|
|
414
|
+
* The component icon (§11.6.4): a small rectangle with two tabs protruding from
|
|
415
|
+
* its LEFT edge, in the top-right corner of the box.
|
|
416
|
+
*
|
|
417
|
+
* Painted as three solid bodies rather than as one outline, because that is what
|
|
418
|
+
* the notation draws — the tabs are little plugs sticking out of the module, and
|
|
419
|
+
* an author who recolours the component recolours them with it.
|
|
420
|
+
*
|
|
421
|
+
* @param x1 the right edge of the body, already inset by half the stroke.
|
|
422
|
+
* @param y0 its top edge, likewise.
|
|
423
|
+
*/
|
|
424
|
+
function paintComponentIcon(ctx, x1, y0, bw, bh) {
|
|
425
|
+
const iw = bw * COMPONENT_ICON.width;
|
|
426
|
+
const ih = bh * COMPONENT_ICON.height;
|
|
427
|
+
const margin = Math.min(bw, bh) * COMPONENT_ICON.margin;
|
|
428
|
+
const tabW = iw * COMPONENT_ICON.tabWidth;
|
|
429
|
+
const tabH = ih * COMPONENT_ICON.tabHeight;
|
|
430
|
+
// The tabs hang off the left of the body, so the body is indented by their
|
|
431
|
+
// width: the whole icon still fits inside the margin it was given.
|
|
432
|
+
const left = x1 - margin - iw;
|
|
433
|
+
const top = y0 + margin;
|
|
434
|
+
if (!(iw > 0) || !(ih > 0) || left - tabW < 0)
|
|
435
|
+
return;
|
|
436
|
+
solidRect(ctx, left, top, iw, ih);
|
|
437
|
+
for (const at of COMPONENT_ICON.tabTop) {
|
|
438
|
+
solidRect(ctx, left - tabW / 2, top + ih * at, tabW, tabH);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* The artifact icon (§19.3.4): a sheet of paper with its top-right corner turned
|
|
443
|
+
* down, in the same corner of the box as the component's.
|
|
444
|
+
*
|
|
445
|
+
* The same silhouette as the note glyph, two orders of magnitude smaller and
|
|
446
|
+
* standing for something else entirely — which is the notation's doing, not a
|
|
447
|
+
* shortcut: a note is a piece of paper pinned to a diagram, an artifact is a
|
|
448
|
+
* file the system ships.
|
|
449
|
+
*/
|
|
450
|
+
function paintArtifactIcon(ctx, x1, y0, bw, bh) {
|
|
451
|
+
const iw = bw * ARTIFACT_ICON.width;
|
|
452
|
+
const ih = bh * ARTIFACT_ICON.height;
|
|
453
|
+
const margin = Math.min(bw, bh) * ARTIFACT_ICON.margin;
|
|
454
|
+
const fold = Math.min(iw, ih) * ARTIFACT_ICON.fold;
|
|
455
|
+
const left = x1 - margin - iw;
|
|
456
|
+
const top = y0 + margin;
|
|
457
|
+
if (!(iw > 0) || !(ih > 0) || left < 0)
|
|
458
|
+
return;
|
|
459
|
+
ctx.beginPath();
|
|
460
|
+
ctx.moveTo(left, top);
|
|
461
|
+
ctx.lineTo(left + iw - fold, top);
|
|
462
|
+
ctx.lineTo(left + iw, top + fold);
|
|
463
|
+
ctx.lineTo(left + iw, top + ih);
|
|
464
|
+
ctx.lineTo(left, top + ih);
|
|
465
|
+
ctx.closePath();
|
|
466
|
+
ctx.fill();
|
|
467
|
+
ctx.stroke();
|
|
468
|
+
// The fold, as two lines — the same way the note draws its own, and for the
|
|
469
|
+
// same reason: a shaded flap would claim a light source UML has none of.
|
|
470
|
+
ctx.beginPath();
|
|
471
|
+
ctx.moveTo(left + iw - fold, top);
|
|
472
|
+
ctx.lineTo(left + iw - fold, top + fold);
|
|
473
|
+
ctx.lineTo(left + iw, top + fold);
|
|
474
|
+
ctx.stroke();
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* The stencil stack, in PROPORTION when the box is too short to hold it.
|
|
478
|
+
*
|
|
479
|
+
* `umlCompartmentBoxes` is written in absolute units — an 8-unit margin, a 16px
|
|
480
|
+
* name line, a 6-unit gap, three 13px attribute lines — because a classifier at
|
|
481
|
+
* its own size must be ruled where the specification rules it, whatever it has
|
|
482
|
+
* been dragged to. A box SHORTER than the stack those absolutes need is the one
|
|
483
|
+
* case they cannot answer: every separator lands past the bottom edge,
|
|
484
|
+
* {@link paintGlyph} clips them all, and a class drawn small is a bare
|
|
485
|
+
* rectangle with no compartments — the picture the notation is told apart BY.
|
|
486
|
+
*
|
|
487
|
+
* So under its natural height the stack is scaled rather than clipped. Nothing
|
|
488
|
+
* moves at or above that height: `squeeze` is 1 and every offset is the number
|
|
489
|
+
* it always was.
|
|
490
|
+
*
|
|
491
|
+
* ## A RENDER-time answer, and only that
|
|
492
|
+
*
|
|
493
|
+
* The squeeze lives here rather than in `component.ts` on purpose. What
|
|
494
|
+
* `umlCompartmentBoxes` answers is where a node's TEXT TIERS go, and the
|
|
495
|
+
* caller that asks with line counts — `UmlCompartmentWatcher` — reads an
|
|
496
|
+
* overflowing stack as its cue to GROW the node, which is the right answer for
|
|
497
|
+
* words an author has typed. This asks a different question: where does the
|
|
498
|
+
* renderer stroke a rule INSIDE the body it has, on a box nothing is going to
|
|
499
|
+
* grow. A legend swatch is that box, and so is any node an author has dragged
|
|
500
|
+
* under its own stack.
|
|
501
|
+
*/
|
|
502
|
+
function umlSqueezedBoxes(kind, w, h) {
|
|
503
|
+
const natural = umlStackHeight(kind, {});
|
|
504
|
+
if (!natural || !(natural > 0) || h >= natural) {
|
|
505
|
+
return umlCompartmentBoxes(kind, 0, 0, w, h);
|
|
506
|
+
}
|
|
507
|
+
// Laid out at the height the stencil wants, then scaled into the box it has
|
|
508
|
+
// — so the three tiers keep the proportions §11.4.4 draws them in rather
|
|
509
|
+
// than each being clamped on its own.
|
|
510
|
+
const squeeze = h / natural;
|
|
511
|
+
const full = umlCompartmentBoxes(kind, 0, 0, w, natural);
|
|
512
|
+
return {
|
|
513
|
+
...full,
|
|
514
|
+
name: { ...full.name, y: full.name.y * squeeze, h: full.name.h * squeeze },
|
|
515
|
+
splits: full.splits.map(split => split * squeeze),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* The compartment layout to stroke this node's separators from: the one its
|
|
520
|
+
* TIERS describe if they can be reached, and the default stack otherwise.
|
|
521
|
+
*
|
|
522
|
+
* ## Why the tiers and not the line counts
|
|
523
|
+
*
|
|
524
|
+
* `UmlCompartmentWatcher` grows a classifier whose attributes tier has
|
|
525
|
+
* overflowed and moves the three texts to the boxes the taller stack yields —
|
|
526
|
+
* so after an edit the compartments are no longer the default three-line stack a
|
|
527
|
+
* rectangle implies, and a renderer still drawing that stack would rule a line
|
|
528
|
+
* straight through the fourth attribute.
|
|
529
|
+
*
|
|
530
|
+
* It could be told the LINE COUNTS and recompute the same walk. It reads the
|
|
531
|
+
* tiers' own boxes instead, for two reasons. It is cheaper — `deserializedXYWH`
|
|
532
|
+
* is cached on the element, while a line count means materializing every tier's
|
|
533
|
+
* `Y.Text` into a string on every frame of every pan — and it cannot disagree
|
|
534
|
+
* with what the reader sees: a separator IS the boundary between two
|
|
535
|
+
* compartments, and reading the boundary off the compartments is the shortest
|
|
536
|
+
* possible route to that. An author who nudges a tier by hand takes the rule
|
|
537
|
+
* with them, which is the same answer.
|
|
538
|
+
*
|
|
539
|
+
* ## The ceiling
|
|
540
|
+
*
|
|
541
|
+
* ponytail: the tiers are read in the node's own unrotated frame, so a ROTATED
|
|
542
|
+
* `umlNode` falls back to the default stack. A UML group cannot be rotated
|
|
543
|
+
* (`GroupElementModel.rotate` is a constant 0), so this only reaches a shape
|
|
544
|
+
* somebody rotated out of its own group — at which point the words are not
|
|
545
|
+
* turning with it either and the picture is already the author's problem.
|
|
546
|
+
* Raising it means projecting each tier's box through `-rotate` about the
|
|
547
|
+
* node's centre, which is four lines nobody has asked for yet.
|
|
548
|
+
*/
|
|
549
|
+
export function umlNodeCompartments(model, w, h) {
|
|
550
|
+
const defaults = umlSqueezedBoxes(model.kind, w, h);
|
|
551
|
+
const group = model.group;
|
|
552
|
+
// No splits at all is a kind that is a PICTURE (a package, an actor, a cube):
|
|
553
|
+
// there are no compartments to read off, and nothing below would mean
|
|
554
|
+
// anything. A rotated shape is the documented ceiling above.
|
|
555
|
+
if (defaults.splits.length === 0 || model.rotate || !group)
|
|
556
|
+
return defaults;
|
|
557
|
+
const children = group.childElements;
|
|
558
|
+
const component = umlComponentSiblings({ id: group.id, childIds: group.childIds }, children);
|
|
559
|
+
// The BODY tier is what both separators hang off: the line above it is where
|
|
560
|
+
// the name compartment stops, the line below it where the operations start.
|
|
561
|
+
// Its role is `uml:attributes` on all seven compartmented kinds — a slot list,
|
|
562
|
+
// a component's parts, a state's activities included (`component.ts`).
|
|
563
|
+
const body = children.find(child => child.id === component.attributes?.id);
|
|
564
|
+
if (!body)
|
|
565
|
+
return defaults;
|
|
566
|
+
const [nodeX, nodeY] = model.deserializedXYWH;
|
|
567
|
+
const top = body.y - nodeY;
|
|
568
|
+
const bottom = top + body.h;
|
|
569
|
+
// A tier dragged clean off its node says nothing useful about where a line
|
|
570
|
+
// INSIDE the node goes.
|
|
571
|
+
if (!(top > 0) || !(bottom > top))
|
|
572
|
+
return defaults;
|
|
573
|
+
const name = children.find(child => child.id === component.name?.id);
|
|
574
|
+
return {
|
|
575
|
+
// The name box is read for the one thing it is used for: the rule under an
|
|
576
|
+
// INSTANCE's name (§9.8.4), which has to stay under the words once a
|
|
577
|
+
// two-line name has pushed them down.
|
|
578
|
+
name: name
|
|
579
|
+
? { x: name.x - nodeX, y: name.y - nodeY, w: name.w, h: name.h }
|
|
580
|
+
: defaults.name,
|
|
581
|
+
// How MANY separators is the notation's answer and the default walk already
|
|
582
|
+
// made it — one for the kinds §9.8.4 / §11.6.4 / §19.3.4 / §14.2.4 draw as a
|
|
583
|
+
// name over a single body, two for a classifier. Only the positions are read
|
|
584
|
+
// off the tier.
|
|
585
|
+
splits: defaults.splits.length === 1 ? [top] : [top, bottom],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
export const umlNode = (model, ctx, matrix, renderer, rc, bound) => {
|
|
589
|
+
const [, , w, h] = model.deserializedXYWH;
|
|
590
|
+
const cx = w / 2;
|
|
591
|
+
const cy = h / 2;
|
|
592
|
+
// Capture the element-local transform BEFORE the shape renderer mutates the
|
|
593
|
+
// matrix, so the glyph can be drawn in the same space afterwards.
|
|
594
|
+
const glyphMatrix = DOMMatrix.fromMatrix(matrix)
|
|
595
|
+
.translateSelf(cx, cy)
|
|
596
|
+
.rotateSelf(model.rotate)
|
|
597
|
+
.translateSelf(-cx, -cy);
|
|
598
|
+
// Native shape: fill, stroke and theme, all handled natively.
|
|
599
|
+
shapeRenderer(model, ctx, matrix, renderer, rc, bound);
|
|
600
|
+
const stroke = renderer.getColorValue(model.strokeColor, DefaultTheme.shapeStrokeColor, true);
|
|
601
|
+
const fill = renderer.getColorValue(model.fillColor, DefaultTheme.shapeFillColor, true);
|
|
602
|
+
const strokeWidth = model.strokeWidth || 1;
|
|
603
|
+
ctx.setTransform(glyphMatrix);
|
|
604
|
+
ctx.strokeStyle = stroke;
|
|
605
|
+
ctx.fillStyle = fill;
|
|
606
|
+
ctx.lineWidth = strokeWidth;
|
|
607
|
+
ctx.lineCap = 'round';
|
|
608
|
+
ctx.lineJoin = 'round';
|
|
609
|
+
paintGlyph(model.kind, ctx, w, h, strokeWidth / 2, umlNodeCompartments(model, w, h));
|
|
610
|
+
};
|
|
611
|
+
export const UmlNodeRendererExtension = ElementRendererExtension('umlNode', umlNode);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type UmlNodeElementModel } from '@formicoidea/labre-core/model';
|
|
2
|
+
import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
|
|
3
|
+
/**
|
|
4
|
+
* View for a UML node. Registering it ensures `gfx.view.get(model)` returns a
|
|
5
|
+
* view (required so move / select / connector interactions work).
|
|
6
|
+
*
|
|
7
|
+
* ## Double-clicking the body edits the NAME — which is not on this element
|
|
8
|
+
*
|
|
9
|
+
* A UML artefact is a GROUP: the shape, plus the canvas TEXT elements that carry
|
|
10
|
+
* its words, every one of them edited in place exactly like any other words on
|
|
11
|
+
* the canvas (R16, and the same arrangement C4's recette of 28/08/2026 settled).
|
|
12
|
+
* The shape itself carries no text at all. Which leaves a gesture with nowhere
|
|
13
|
+
* obvious to go: the body is the biggest target in the component, double-clicking
|
|
14
|
+
* a shape is how everybody in this editor starts typing, and the shape's own
|
|
15
|
+
* inline editor would write into the one field that must stay empty — a "shadow"
|
|
16
|
+
* name, invisible under the real one, disagreeing with it, exported by neither.
|
|
17
|
+
*
|
|
18
|
+
* So the gesture is ROUTED: a double-click on the body opens the NAME child's
|
|
19
|
+
* own editor, `mountTextElementEditor` — the very same call `TextElementView`
|
|
20
|
+
* makes when you double-click those words directly. The author gets one editor
|
|
21
|
+
* for one name whichever half of the component they aimed at.
|
|
22
|
+
*
|
|
23
|
+
* ## `uml:name`, `uml:label` or `uml:lifeline-ident`, and why all three
|
|
24
|
+
*
|
|
25
|
+
* A compartmented artefact's first tier is `uml:name` — it is the NAME
|
|
26
|
+
* COMPARTMENT of §11.4.4, a rectangle with a rule under it. An actor's and a use
|
|
27
|
+
* case's single word is `uml:label`: the same tier, named differently because
|
|
28
|
+
* there is no compartment for it to be a compartment OF. A lifeline's head is
|
|
29
|
+
* `uml:lifeline-ident`, a third time: §17.3.4 prints a grammar for what goes in
|
|
30
|
+
* it and the validation pack reads that grammar and no other (`roles.ts`). The
|
|
31
|
+
* gesture is one gesture, so it accepts any of the three and takes whichever the
|
|
32
|
+
* group holds — the kind is not consulted, which means an artefact regrouped by
|
|
33
|
+
* hand still opens on the words it actually has.
|
|
34
|
+
*
|
|
35
|
+
* ## …except on an element that has no text child
|
|
36
|
+
*
|
|
37
|
+
* A node whose group was released and whose texts were deleted lands on
|
|
38
|
+
* `mountShapeTextEditor` and gets the native behaviour rather than nothing at
|
|
39
|
+
* all. Same fallback C4's node view keeps, and for the same reason: a gesture
|
|
40
|
+
* that silently does nothing reads as a broken editor.
|
|
41
|
+
*
|
|
42
|
+
* Hit testing is `UmlNodeElementModel.includesPoint`'s doing: the glyph-bodied
|
|
43
|
+
* kinds are created unfilled, and an empty unfilled shape would normally be a
|
|
44
|
+
* target only near its border. The override forces the interior test, so the
|
|
45
|
+
* whole body answers this gesture on all eight kinds.
|
|
46
|
+
*/
|
|
47
|
+
export declare class UmlNodeView extends GfxElementModelView<UmlNodeElementModel> {
|
|
48
|
+
#private;
|
|
49
|
+
static type: string;
|
|
50
|
+
onCreated(): void;
|
|
51
|
+
}
|