@formicoidea/labre-framework-c4 0.33.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.
Files changed (61) hide show
  1. package/dist/actions.d.ts +179 -0
  2. package/dist/actions.js +375 -0
  3. package/dist/background.d.ts +77 -0
  4. package/dist/background.js +223 -0
  5. package/dist/commands.d.ts +4 -0
  6. package/dist/commands.js +221 -0
  7. package/dist/component.d.ts +192 -0
  8. package/dist/component.js +188 -0
  9. package/dist/consts.d.ts +331 -0
  10. package/dist/consts.js +384 -0
  11. package/dist/descriptor.d.ts +12 -0
  12. package/dist/descriptor.js +10 -0
  13. package/dist/effects.d.ts +9 -0
  14. package/dist/effects.js +6 -0
  15. package/dist/element-renderer.d.ts +18 -0
  16. package/dist/element-renderer.js +14 -0
  17. package/dist/element-view.d.ts +51 -0
  18. package/dist/element-view.js +146 -0
  19. package/dist/export.d.ts +184 -0
  20. package/dist/export.js +454 -0
  21. package/dist/index.d.ts +16 -0
  22. package/dist/index.js +52 -0
  23. package/dist/interchange.d.ts +74 -0
  24. package/dist/interchange.js +143 -0
  25. package/dist/legend.d.ts +37 -0
  26. package/dist/legend.js +123 -0
  27. package/dist/levels.d.ts +70 -0
  28. package/dist/levels.js +46 -0
  29. package/dist/morph.d.ts +89 -0
  30. package/dist/morph.js +229 -0
  31. package/dist/node/node-renderer.d.ts +6 -0
  32. package/dist/node/node-renderer.js +304 -0
  33. package/dist/node/node-view.d.ts +45 -0
  34. package/dist/node/node-view.js +80 -0
  35. package/dist/node/type-line-watcher.d.ts +70 -0
  36. package/dist/node/type-line-watcher.js +142 -0
  37. package/dist/presets.d.ts +84 -0
  38. package/dist/presets.js +149 -0
  39. package/dist/profiles.d.ts +2 -0
  40. package/dist/profiles.js +177 -0
  41. package/dist/roles.d.ts +116 -0
  42. package/dist/roles.js +303 -0
  43. package/dist/rules.d.ts +95 -0
  44. package/dist/rules.js +1261 -0
  45. package/dist/toolbar/c4-menu.d.ts +11 -0
  46. package/dist/toolbar/c4-menu.js +14 -0
  47. package/dist/toolbar/c4-senior-button.d.ts +19 -0
  48. package/dist/toolbar/c4-senior-button.js +23 -0
  49. package/dist/toolbar/config.d.ts +150 -0
  50. package/dist/toolbar/config.js +436 -0
  51. package/dist/toolbar/icons.d.ts +90 -0
  52. package/dist/toolbar/icons.js +157 -0
  53. package/dist/toolbar/senior-tool.d.ts +1 -0
  54. package/dist/toolbar/senior-tool.js +11 -0
  55. package/dist/translations.d.ts +18 -0
  56. package/dist/translations.js +42 -0
  57. package/dist/type-line.d.ts +175 -0
  58. package/dist/type-line.js +244 -0
  59. package/dist/view.d.ts +33 -0
  60. package/dist/view.js +148 -0
  61. package/package.json +34 -0
package/dist/consts.js ADDED
@@ -0,0 +1,384 @@
1
+ /**
2
+ * Visual constants for the C4 pack.
3
+ *
4
+ * Unlike BPMN — a black-and-white notation where the SHAPE carries the meaning
5
+ * and colour is decoration — C4's official stencil is a colour code: the four
6
+ * levels are four blues, from the near-navy of a person down to the pale wash of
7
+ * a component, and anything outside the scope of the diagram is grey. That is
8
+ * the one thing a reader uses to tell a container from a component when both are
9
+ * rounded rectangles with words in them, so the palette below IS the notation
10
+ * and is written down as data rather than left to whoever draws next.
11
+ *
12
+ * Every hex, every size and every radius below is read off the PO's own
13
+ * reference model — the Visio-exported `C4Model_default.svg` stencil — rather
14
+ * than approximated, and the file's units are carried through at ×2 so that a
15
+ * default node is a comfortable size on a canvas. Where a number is a fraction
16
+ * of the node box rather than an absolute, it lives in the renderer beside the
17
+ * path it shapes. Every value here is a creation-time DEFAULT; each one is an
18
+ * editable shape property afterwards, exactly as in BPMN.
19
+ */
20
+ /** The stencil's own unit → model unit factor. Every absolute below is ×2. */
21
+ export const STENCIL_SCALE = 2;
22
+ const WHITE = '#ffffff';
23
+ const BLACK = '#000000';
24
+ /**
25
+ * The grey every EXTERNAL element is drawn in — a person or a system somebody
26
+ * else owns. One grey for both, because "outside the scope of this diagram" is
27
+ * one statement and the level of the thing outside it is not the point.
28
+ */
29
+ const EXTERNAL = {
30
+ fill: '#999999',
31
+ border: '#8b8b8b',
32
+ text: WHITE,
33
+ };
34
+ /**
35
+ * The palette, per kind. TOTAL over {@link C4NodeKind} by its type, so a kind
36
+ * added to the model cannot land without being given a colour.
37
+ *
38
+ * The four levels run light as they go IN: a person is the darkest thing on the
39
+ * page (#08427b), a software system one step lighter (#1168bd), every container
40
+ * lighter again (#438dd5) and a component palest of all (#85bbf0) — which is
41
+ * also the one that takes black text, because white on that wash is unreadable.
42
+ * `database`, `mobile` and `browser` are CONTAINERS and take the container's
43
+ * colour exactly: what makes them different is the silhouette the renderer
44
+ * draws, never the level, and a fourth blue would say otherwise.
45
+ *
46
+ * Every border is the stencil's own darker shade of its fill, lifted verbatim
47
+ * from the reference model's stylesheet (`.st1`, `.st5`, `.st6`, `.st7`,
48
+ * `.st22`) rather than darkened by eye — which is what makes the two decorated
49
+ * containers work at all: `mobile` and `browser` paint their BEZEL in the border
50
+ * colour and their SCREEN in the fill, so the pair has to be the stencil's pair.
51
+ */
52
+ export const NODE_PALETTE = {
53
+ person: { fill: '#08427b', border: '#073b6f', text: WHITE },
54
+ 'person-ext': EXTERNAL,
55
+ system: { fill: '#1168bd', border: '#1864ad', text: WHITE },
56
+ 'system-ext': EXTERNAL,
57
+ container: { fill: '#438dd5', border: '#3d81c3', text: WHITE },
58
+ database: { fill: '#438dd5', border: '#3d81c3', text: WHITE },
59
+ mobile: { fill: '#438dd5', border: '#3d81c3', text: WHITE },
60
+ browser: { fill: '#438dd5', border: '#3d81c3', text: WHITE },
61
+ component: { fill: '#85bbf0', border: '#78a8d8', text: BLACK },
62
+ };
63
+ /** Border weight of every node — one line (stencil `stroke-width:1`), ×2. */
64
+ export const NODE_STROKE_WIDTH = 2;
65
+ /**
66
+ * Corner radius of each kind's OUTER body, in model units.
67
+ *
68
+ * A table rather than one number, because the stencil is not uniform and the
69
+ * difference is legible: `system`, `system-ext`, `container` and `component` are
70
+ * plain `<rect>`s with **no `rx` at all** — square corners — while the two
71
+ * decorated containers are rounded, the phone noticeably (`rx="4.252"`) and the
72
+ * browser window barely (`rx="1.4173"`). The three glyph-bodied kinds carry `0`
73
+ * because their native rect paints nothing: a person's shoulders and a
74
+ * cylinder's lid are curves the glyph draws itself, in proportion to the box.
75
+ *
76
+ * This corrects the pack's first pass, which rounded every kind at 10 — a
77
+ * plausible house style, and one the reference model does not draw.
78
+ */
79
+ export const NODE_RADIUS = {
80
+ person: 0,
81
+ 'person-ext': 0,
82
+ system: 0,
83
+ 'system-ext': 0,
84
+ container: 0,
85
+ database: 0,
86
+ mobile: 4.252 * STENCIL_SCALE,
87
+ browser: 1.4173 * STENCIL_SCALE,
88
+ component: 0,
89
+ };
90
+ /**
91
+ * The three text tiers, at the stencil's own sizes (10 / 6 / 8) ×2.
92
+ *
93
+ * All three are creation-time DEFAULTS the author can change afterwards, and all
94
+ * three are now CANVAS TEXT ELEMENTS: since the PO's recette of 28/08/2026 a C4
95
+ * component is a group holding the shape and its three lines of words — the
96
+ * name included — so every tier is ordinary text with its own toolbar rather
97
+ * than something a renderer painted and nobody could type on.
98
+ *
99
+ * The name was the shape's native inner text for one iteration and is not any
100
+ * more, which is the second half of that recette: two kinds of text in one
101
+ * component meant two editors, two toolbars and two sets of rules for the same
102
+ * three lines. Now there is one of each.
103
+ *
104
+ * The ladder 20 / 16 / 12 is the notation and not typography — the type line is
105
+ * smaller than the name on every C4 diagram ever drawn — but it is a ladder an
106
+ * author can climb off, which is the price of letting them write on the picture.
107
+ */
108
+ export const TITLE_FONT_SIZE = 10 * STENCIL_SCALE;
109
+ export const TYPE_FONT_SIZE = 6 * STENCIL_SCALE;
110
+ export const DESCRIPTION_FONT_SIZE = 8 * STENCIL_SCALE;
111
+ /**
112
+ * One line box, as a multiple of its own font size — what the creation site
113
+ * measures a tier's HEIGHT in.
114
+ *
115
+ * The stencil states its tiers as baselines, which is the right unit for a
116
+ * renderer painting into a box and the wrong one for a creation site placing an
117
+ * element: a text element is a rectangle, and where its first baseline lands
118
+ * inside that rectangle is the text renderer's business, not this file's. So the
119
+ * stencil's baseline steps are re-read here as line boxes, which is the same
120
+ * geometry counted from the other end.
121
+ */
122
+ export const TIER_LINE_HEIGHT = 1.2;
123
+ /**
124
+ * How many lines each of the two wrapping tiers opens with.
125
+ *
126
+ * The title gets TWO, and that is what grew the element (see {@link NODE_BOX}):
127
+ * "Internet Banking System" is 23 characters, and 23 characters at 20px do not
128
+ * fit across 187 units of usable width. A one-line title box would have meant
129
+ * every real system name spilling out of its own tier on the day it was typed —
130
+ * which is precisely the cramped stack the PO's recette was about.
131
+ *
132
+ * The description gets two as well, which is what the stencil's own sentences
133
+ * run to. Neither is a limit: a longer text wraps inside the tier's width and
134
+ * grows the box downward, and the group grows with it, so a component keeps
135
+ * containing its own words.
136
+ */
137
+ export const TITLE_LINES = 2;
138
+ export const DESCRIPTION_LINES = 2;
139
+ /**
140
+ * The vertical rhythm of the stack, in model units.
141
+ *
142
+ * Absolutes rather than multiples of a font size, and deliberately so: what the
143
+ * eye reads here is the SPACE between three blocks of different sizes, and a gap
144
+ * expressed as 0.7em of whichever tier happens to be below it changes meaning
145
+ * every time somebody resizes one of them. These three numbers are the layout.
146
+ *
147
+ * The two gaps are different on purpose. The name and its type line are ONE
148
+ * heading — `Web Application` / `[Container: Java]` is a single statement over
149
+ * two lines — so they sit close. The description is a different statement, and
150
+ * the wider gap under the type line is the stencil's own blank line: it is what
151
+ * keeps a sentence from reading as a fourth tier of the heading.
152
+ *
153
+ * {@link TIER_MARGIN} is equal top and bottom, which is what makes the stack sit
154
+ * in its box rather than in the top of it.
155
+ */
156
+ export const TIER_MARGIN = 24;
157
+ export const TITLE_TYPE_GAP = 8;
158
+ export const TYPE_DESCRIPTION_GAP = 16;
159
+ /** Side inset the three text tiers sit within, as a fraction of the node width. */
160
+ export const TIER_SIDE_INSET = 0.06;
161
+ /**
162
+ * The height of the three tiers and the two gaps between them — the number the
163
+ * default element size is DERIVED from rather than fitted to.
164
+ *
165
+ * 48 + 8 + 14.4 + 16 + 38.4 = 124.8. Written as the sum it is so that changing
166
+ * a font size or a gap moves the box with it: a rhythm and a footprint that can
167
+ * disagree is a rhythm that will.
168
+ */
169
+ export const TIER_STACK_HEIGHT = TITLE_FONT_SIZE * TIER_LINE_HEIGHT * TITLE_LINES +
170
+ TITLE_TYPE_GAP +
171
+ TYPE_FONT_SIZE * TIER_LINE_HEIGHT +
172
+ TYPE_DESCRIPTION_GAP +
173
+ DESCRIPTION_FONT_SIZE * TIER_LINE_HEIGHT * DESCRIPTION_LINES;
174
+ /**
175
+ * The sentence a fresh description prompts the author with.
176
+ *
177
+ * The stencil's own placeholder, and a PROMPT rather than a value: every tier of
178
+ * a C4 component exists from the moment it is drawn (PO arbitration,
179
+ * 28/08/2026), so the author meets three lines of stencil rather than a box and
180
+ * two invisible slots somebody has to tell them about. The exporter compares
181
+ * against it to decide that nothing has been stated yet — see
182
+ * `C4_TYPE_PLACEHOLDER` in `type-line.ts` for the same call on the other tier.
183
+ */
184
+ export const DESCRIPTION_PLACEHOLDER = 'description';
185
+ /**
186
+ * How far the person's head stands clear ABOVE its body, in model units.
187
+ *
188
+ * The stencil's own `47.767` at ×2, solved off the silhouette path (`mID 1`):
189
+ * its head arc is drawn with `large-arc-flag=1` about a centre 21.26 units above
190
+ * the body's top edge, with `ry=26.504`, so the head clears the body by the sum
191
+ * of the two. Independent of how tall the BODY is, which is what lets the body
192
+ * grow with the text rhythm below without moving the head.
193
+ */
194
+ export const PERSON_HEAD_CLEARANCE = 47.767 * STENCIL_SCALE;
195
+ /**
196
+ * Default node sizes (model units) per kind.
197
+ *
198
+ * ## One footprint, and one exception the stencil itself draws
199
+ *
200
+ * The reference model gives every element the SAME box — `106.3 × 74.409`, a
201
+ * `v:textRect` repeated verbatim on the system, the container, the component,
202
+ * the database, the phone and the browser window. Seven of the nine kinds take
203
+ * one footprint, and a row of C4 elements lining up without anybody arranging
204
+ * them is not a convenience, it is what makes a level readable.
205
+ *
206
+ * ## Why the box is taller than the stencil's, and by exactly how much
207
+ *
208
+ * The WIDTH is the stencil's, untouched: `106.3 × 2 = 212.6`. Widening it would
209
+ * change every glyph with it — a person's head radius is derived from the width
210
+ * — and the reference proportions are the one thing the recette of 27/08 was
211
+ * about.
212
+ *
213
+ * The HEIGHT is derived from the words instead, which is the PO's call of
214
+ * 28/08/2026: grow the shapes if that is what it takes to have room to write.
215
+ * `74.409 × 2 = 148.8` was the stencil's textRect for a box holding a name it
216
+ * could paint in a single line at whatever size it liked. This one holds three
217
+ * REAL text elements, at fixed sizes, with margins and gaps a reader can see —
218
+ * and the title alone needs two lines, because a system name is routinely longer
219
+ * than 187 units of usable width at 20px. So the height is
220
+ * {@link TIER_STACK_HEIGHT} plus a margin at each end: **212.6 × 172.8**, up
221
+ * from 212.6 × 148.8.
222
+ *
223
+ * Derived rather than chosen, so the box can never disagree with what it holds:
224
+ * change a tier's size or a gap and the footprint follows.
225
+ *
226
+ * ## The person
227
+ *
228
+ * `person` and `person-ext` are the exception, and it is the FILE's exception,
229
+ * not a preference. Their silhouette is one path (`mID 1`) whose head arc is
230
+ * drawn about a centre above the body's top edge, so the head stands
231
+ * {@link PERSON_HEAD_CLEARANCE} clear of a body that is itself the standard box.
232
+ * The stencil's own sheet shows it: the person's group is translated further
233
+ * down the page than the system beside it, precisely to make room. The body
234
+ * grows with everything else, so the person is now **212.6 × 268.3**.
235
+ *
236
+ * Forcing a person into the boxed footprint was considered and rejected: the
237
+ * head is a CIRCLE (`rx 26.362`, `ry 26.504`) and stays one only at the
238
+ * silhouette's own aspect ratio — squeezed into a box far wider than it is tall
239
+ * it becomes a flat ellipse, which is the one thing about a C4 person everybody
240
+ * recognises and the one thing that would then be wrong.
241
+ */
242
+ export const NODE_BOX = {
243
+ w: 106.3 * STENCIL_SCALE,
244
+ h: TIER_MARGIN * 2 + TIER_STACK_HEIGHT,
245
+ };
246
+ /** The person's full silhouette — the standard body, plus the head above it. */
247
+ export const PERSON_BOX = {
248
+ w: NODE_BOX.w,
249
+ h: PERSON_HEAD_CLEARANCE + NODE_BOX.h,
250
+ };
251
+ /**
252
+ * Where the person's body top edge sits, as a fraction of the whole silhouette.
253
+ *
254
+ * Derived from the two boxes rather than restated as the stencil's own ratio, so
255
+ * that growing the body cannot leave this pointing at the middle of the head.
256
+ * The renderer derives the same edge from the head RADIUS instead
257
+ * (`PERSON.bodyTopPerHead`), which is the form that survives an element dragged
258
+ * to an aspect ratio the head has to be clamped at; the two agree at the default
259
+ * size. This one is what the creation site needs: it has a box and no glyph, and
260
+ * it has to know where the words can go.
261
+ */
262
+ export const PERSON_BODY_TOP = PERSON_HEAD_CLEARANCE / PERSON_BOX.h;
263
+ export const NODE_SIZE = {
264
+ person: PERSON_BOX,
265
+ 'person-ext': PERSON_BOX,
266
+ system: NODE_BOX,
267
+ 'system-ext': NODE_BOX,
268
+ container: NODE_BOX,
269
+ database: NODE_BOX,
270
+ mobile: NODE_BOX,
271
+ browser: NODE_BOX,
272
+ component: NODE_BOX,
273
+ };
274
+ /**
275
+ * Default inner text per kind.
276
+ *
277
+ * Every kind carries one, unlike BPMN — where an event's meaning IS its glyph
278
+ * and the spec puts its name outside the shape. Here the box is the same box at
279
+ * three of the four levels, so a C4 element with nothing written in it says
280
+ * nothing at all: the words are the artefact.
281
+ */
282
+ export const NODE_LABEL = {
283
+ person: 'Person',
284
+ 'person-ext': 'External person',
285
+ system: 'Software system',
286
+ 'system-ext': 'External system',
287
+ container: 'Container',
288
+ database: 'Database',
289
+ mobile: 'Mobile app',
290
+ browser: 'Web app',
291
+ component: 'Component',
292
+ };
293
+ /* ── The board ─────────────────────────────────────────────────────────── */
294
+ export const FONT_FAMILY = 'Inter, sans-serif';
295
+ /** The card — the same white every framework background paints. */
296
+ export const BOARD_CARD_FILL = '#ffffff';
297
+ export const BOARD_CARD_BORDER = '#d5d9e0';
298
+ export const BOARD_BORDER_WIDTH = 1.5;
299
+ export const BOARD_CORNER_RADIUS = 12;
300
+ export const BOARD_TITLE_FONT_SIZE = 20;
301
+ export const BOARD_TITLE_COLOR = '#262626';
302
+ /**
303
+ * The size a fresh board is created at, and the room its furniture takes.
304
+ *
305
+ * The Context Map board's own numbers: a C4 diagram is the same kind of object —
306
+ * a sheet you spread out and add boxes to as the system is discovered — so it
307
+ * starts at the same size and grows the same way. The top margin is deeper than
308
+ * the other three because that is where the title is written.
309
+ */
310
+ export const BOARD_REF_WIDTH = 1400;
311
+ export const BOARD_REF_HEIGHT = 900;
312
+ export const BOARD_MARGIN = 24;
313
+ export const BOARD_TITLE_MARGIN = 56;
314
+ /* ── The boundary ──────────────────────────────────────────────────────── */
315
+ /**
316
+ * The boundary's frame and its name, at the stencil's own values ×2 (`.st20`
317
+ * and `.st8` in the reference model).
318
+ *
319
+ * All four numbers changed with the PO's recette of 27/08/2026, and all four
320
+ * were house style before it: the frame was a mid grey at weight 2 with rounded
321
+ * corners, and the stencil draws `#444444` at weight 1 with SQUARE ones. The
322
+ * name is black rather than grey and a size larger, which is what it takes to
323
+ * read a boundary's name over the diagram it is drawn on top of.
324
+ */
325
+ export const BOUNDARY_STROKE = '#444444';
326
+ export const BOUNDARY_WIDTH = 0.5 * STENCIL_SCALE;
327
+ export const BOUNDARY_CORNER_RADIUS = 0;
328
+ /** The dash, in model units: the stencil's `stroke-dasharray:7.5,4.5`, ×2. */
329
+ export const BOUNDARY_DASH = [
330
+ 7.5 * STENCIL_SCALE,
331
+ 4.5 * STENCIL_SCALE,
332
+ ];
333
+ export const BOUNDARY_NAME_FONT_SIZE = 10 * STENCIL_SCALE;
334
+ export const BOUNDARY_NAME_COLOR = '#000000';
335
+ /**
336
+ * The bracket line under a boundary's name — `[Software System]`, `[Container]`
337
+ * — at the stencil's own 6px ×2, on the same 1.917em baseline step it uses.
338
+ *
339
+ * Vocabulary, not user text: the words come from the declaration's `labelKey`
340
+ * and are translatable through the host's catalogue, which is also what keeps
341
+ * them out of the in-place editor. A boundary's NAME is the author's; what kind
342
+ * of boundary it is, is the notation's.
343
+ */
344
+ export const BOUNDARY_TYPE_FONT_SIZE = 6 * STENCIL_SCALE;
345
+ export const BOUNDARY_TYPE_STEP = 1.917 * BOUNDARY_TYPE_FONT_SIZE;
346
+ /** How far above the bottom edge of the plot the name's baseline sits. */
347
+ export const BOUNDARY_NAME_INSET = 8;
348
+ export const BOUNDARY_REF_WIDTH = 520;
349
+ export const BOUNDARY_REF_HEIGHT = 360;
350
+ /**
351
+ * The boundary's inset. Small and equal on all four sides: unlike a board, a
352
+ * boundary has no furniture to make room for — the name is written INSIDE the
353
+ * bottom-left corner of the plot, over the diagram, exactly as C4 draws it.
354
+ */
355
+ export const BOUNDARY_MARGIN = 12;
356
+ /**
357
+ * The wording a fresh boundary is named with, per variant.
358
+ *
359
+ * The variant changes the DEFAULT NAME and nothing else: both are the same
360
+ * dashed rectangle, and C4 tells them apart by what is written under the corner.
361
+ * Read by the creation site (which writes `name`), never by the renderer — the
362
+ * declaration draws whatever the user's own `name` says, so a boundary renamed
363
+ * on the canvas keeps its words whatever its variant.
364
+ */
365
+ export const BOUNDARY_LABEL = {
366
+ system: 'System boundary',
367
+ container: 'Container boundary',
368
+ };
369
+ /* ── The relationship ──────────────────────────────────────────────────── */
370
+ /**
371
+ * Relationship connector preset — the dashed arrow C4 draws between elements.
372
+ *
373
+ * DASHED and not solid, which is the stencil's own choice and worth keeping:
374
+ * every line on a C4 diagram is a relationship, so the dash is not a
375
+ * distinction between two kinds of line but the house style of the one kind
376
+ * there is. Grey rather than black for the same reason the boundary is: the
377
+ * boxes are the statement, the arrows are the grammar between them.
378
+ *
379
+ * `#444444` at weight 1.5 is the stencil's `.st15` (`stroke:#444444`,
380
+ * `stroke-width:0.75`) at ×2, and it is the SAME grey the boundary frame is
381
+ * drawn in — one neutral for everything that is not an element.
382
+ */
383
+ export const RELATIONSHIP_STROKE = '#444444';
384
+ export const RELATIONSHIP_WIDTH = 0.75 * STENCIL_SCALE;
@@ -0,0 +1,12 @@
1
+ import { C4RenderViewExtension, C4ViewExtension } from './view.js';
2
+ /** Host wiring for the c4 framework. */
3
+ export declare const c4Framework: {
4
+ readonly flag: "c4";
5
+ readonly telemetryKey: "c4";
6
+ readonly extensions: readonly [{
7
+ readonly viewExtension: typeof C4RenderViewExtension;
8
+ }, {
9
+ readonly flag: "c4";
10
+ readonly viewExtension: typeof C4ViewExtension;
11
+ }];
12
+ };
@@ -0,0 +1,10 @@
1
+ import { C4RenderViewExtension, C4ViewExtension } from './view.js';
2
+ /** Host wiring for the c4 framework. */
3
+ export const c4Framework = {
4
+ flag: 'c4',
5
+ telemetryKey: 'c4',
6
+ extensions: [
7
+ { viewExtension: C4RenderViewExtension },
8
+ { flag: 'c4', viewExtension: C4ViewExtension },
9
+ ],
10
+ };
@@ -0,0 +1,9 @@
1
+ import { EdgelessC4Menu } from './toolbar/c4-menu.js';
2
+ import { EdgelessC4SeniorButton } from './toolbar/c4-senior-button.js';
3
+ export declare function effects(): void;
4
+ declare global {
5
+ interface HTMLElementTagNameMap {
6
+ 'edgeless-c4-menu': EdgelessC4Menu;
7
+ 'edgeless-c4-senior-button': EdgelessC4SeniorButton;
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ import { EdgelessC4Menu } from './toolbar/c4-menu.js';
2
+ import { EdgelessC4SeniorButton } from './toolbar/c4-senior-button.js';
3
+ export function effects() {
4
+ customElements.define('edgeless-c4-menu', EdgelessC4Menu);
5
+ customElements.define('edgeless-c4-senior-button', EdgelessC4SeniorButton);
6
+ }
@@ -0,0 +1,18 @@
1
+ import { type ElementRenderer } from '@formicoidea/labre-core/blocks/surface';
2
+ import type { C4BoardElementModel, C4BoundaryElementModel } from '@formicoidea/labre-core/model';
3
+ /**
4
+ * Canvas renderers for the two C4 frames.
5
+ *
6
+ * There is no C4 drawing code here: both are INSTANTIATIONS of the
7
+ * framework-background primitive, configured by the declarations in
8
+ * `background.ts`. Exported as functions as well as extensions because the
9
+ * fidelity suite drives them directly with a canvas stub.
10
+ */
11
+ export declare const c4Board: ElementRenderer<C4BoardElementModel>;
12
+ export declare const C4BoardRendererExtension: import("@formicoidea/labre-core/store").ExtensionType & {
13
+ identifier: import("@formicoidea/labre-core/_pkgs/global/di").ServiceIdentifier<ElementRenderer<C4BoardElementModel>>;
14
+ };
15
+ export declare const c4Boundary: ElementRenderer<C4BoundaryElementModel>;
16
+ export declare const C4BoundaryRendererExtension: import("@formicoidea/labre-core/store").ExtensionType & {
17
+ identifier: import("@formicoidea/labre-core/_pkgs/global/di").ServiceIdentifier<ElementRenderer<C4BoundaryElementModel>>;
18
+ };
@@ -0,0 +1,14 @@
1
+ import { createFrameworkBackgroundRenderer, ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
2
+ import { C4_BOARD_BACKGROUND, C4_BOUNDARY_BACKGROUND } from './background.js';
3
+ /**
4
+ * Canvas renderers for the two C4 frames.
5
+ *
6
+ * There is no C4 drawing code here: both are INSTANTIATIONS of the
7
+ * framework-background primitive, configured by the declarations in
8
+ * `background.ts`. Exported as functions as well as extensions because the
9
+ * fidelity suite drives them directly with a canvas stub.
10
+ */
11
+ export const c4Board = createFrameworkBackgroundRenderer(C4_BOARD_BACKGROUND);
12
+ export const C4BoardRendererExtension = ElementRendererExtension(C4_BOARD_BACKGROUND.type, c4Board);
13
+ export const c4Boundary = createFrameworkBackgroundRenderer(C4_BOUNDARY_BACKGROUND);
14
+ export const C4BoundaryRendererExtension = ElementRendererExtension(C4_BOUNDARY_BACKGROUND.type, c4Boundary);
@@ -0,0 +1,51 @@
1
+ import type { FrameworkBackgroundDef } from '@formicoidea/labre-core/blocks/surface';
2
+ import type { C4BoardElementModel, C4BoundaryElementModel } from '@formicoidea/labre-core/model';
3
+ import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
4
+ /**
5
+ * The one gesture the two C4 frames carry: a double-click on the name edits it
6
+ * in place.
7
+ *
8
+ * Both frames are a card with exactly one editable word on it — the board's
9
+ * title, the boundary's name — so the gesture is written once here and the two
10
+ * views differ only in which declaration they hit-test against. That is the
11
+ * simplified version of `BpmnPoolView`: no lanes, no separators, no armed drag,
12
+ * because neither frame has anything inside it to divide.
13
+ *
14
+ * Which labels exist, where they sit, what they SAY and which are editable all
15
+ * come from the declaration the renderer paints (`backgroundLabelHits`), so a
16
+ * label can never be drawn in one place and clicked in another — the same
17
+ * source `WardleyView` reads, and the reason this class has no coordinates of
18
+ * its own.
19
+ *
20
+ * ponytail: like every other framework view in the library, only `name` may be
21
+ * written. The declarations bind exactly one prop each; the guard is what keeps
22
+ * that true if a second label is ever declared with a prop nobody meant to
23
+ * expose to an in-place editor.
24
+ */
25
+ declare abstract class C4FrameView<T extends C4BoardElementModel | C4BoundaryElementModel> extends GfxElementModelView<T> {
26
+ /** The declaration this view hit-tests against — the one the renderer paints. */
27
+ protected abstract get def(): FrameworkBackgroundDef;
28
+ /** The in-place `<input>` used to edit the name, or null when idle. */
29
+ private _editor;
30
+ onCreated(): void;
31
+ onDestroyed(): void;
32
+ private _onDblClick;
33
+ /**
34
+ * @param current the words currently DRAWN, which is what the user aimed at —
35
+ * never `model.name`, so a frame showing its declared wording opens on that
36
+ * wording rather than on an empty box.
37
+ */
38
+ private _openEditor;
39
+ private _closeEditor;
40
+ }
41
+ /** The sheet a C4 diagram is drawn on. Double-click its title to rename it. */
42
+ export declare class C4BoardView extends C4FrameView<C4BoardElementModel> {
43
+ static type: string;
44
+ protected get def(): FrameworkBackgroundDef;
45
+ }
46
+ /** The dashed frame drawn round part of one. Double-click its name to rename. */
47
+ export declare class C4BoundaryView extends C4FrameView<C4BoundaryElementModel> {
48
+ static type: string;
49
+ protected get def(): FrameworkBackgroundDef;
50
+ }
51
+ export {};
@@ -0,0 +1,146 @@
1
+ import { backgroundLabelHits, EdgelessCRUDIdentifier, hitTestBackgroundLabel, } from '@formicoidea/labre-core/blocks/surface';
2
+ import { TranslationProvider } from '@formicoidea/labre-core/shared/services';
3
+ import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
4
+ import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
5
+ import { C4_BOARD_BACKGROUND, C4_BOUNDARY_BACKGROUND } from './background.js';
6
+ /**
7
+ * The one gesture the two C4 frames carry: a double-click on the name edits it
8
+ * in place.
9
+ *
10
+ * Both frames are a card with exactly one editable word on it — the board's
11
+ * title, the boundary's name — so the gesture is written once here and the two
12
+ * views differ only in which declaration they hit-test against. That is the
13
+ * simplified version of `BpmnPoolView`: no lanes, no separators, no armed drag,
14
+ * because neither frame has anything inside it to divide.
15
+ *
16
+ * Which labels exist, where they sit, what they SAY and which are editable all
17
+ * come from the declaration the renderer paints (`backgroundLabelHits`), so a
18
+ * label can never be drawn in one place and clicked in another — the same
19
+ * source `WardleyView` reads, and the reason this class has no coordinates of
20
+ * its own.
21
+ *
22
+ * ponytail: like every other framework view in the library, only `name` may be
23
+ * written. The declarations bind exactly one prop each; the guard is what keeps
24
+ * that true if a second label is ever declared with a prop nobody meant to
25
+ * expose to an in-place editor.
26
+ */
27
+ class C4FrameView extends GfxElementModelView {
28
+ constructor() {
29
+ super(...arguments);
30
+ /** The in-place `<input>` used to edit the name, or null when idle. */
31
+ this._editor = null;
32
+ }
33
+ onCreated() {
34
+ super.onCreated();
35
+ this.on('dblclick', e => this._onDblClick(e));
36
+ }
37
+ onDestroyed() {
38
+ this._closeEditor();
39
+ super.onDestroyed();
40
+ }
41
+ _onDblClick(e) {
42
+ if (this.gfx.std.store.readonly || this.model.isLocked())
43
+ return;
44
+ const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
45
+ const [bx, by, w, h] = this.model.deserializedXYWH;
46
+ // Element-local coordinates, undoing the element rotation about its centre.
47
+ let lx = mx - bx;
48
+ let ly = my - by;
49
+ const rot = this.model.rotate ?? 0;
50
+ if (rot) {
51
+ const center = [bx + w / 2, by + h / 2];
52
+ const [ux, uy] = rotatePoint([mx, my], center, -rot);
53
+ lx = ux - bx;
54
+ ly = uy - by;
55
+ }
56
+ const hit = hitTestBackgroundLabel(backgroundLabelHits(this.def, this.model, w, h, this.gfx.std.getOptional(TranslationProvider)), lx, ly);
57
+ if (!hit || hit.prop !== 'name')
58
+ return;
59
+ this._openEditor(hit.text, e);
60
+ }
61
+ /**
62
+ * @param current the words currently DRAWN, which is what the user aimed at —
63
+ * never `model.name`, so a frame showing its declared wording opens on that
64
+ * wording rather than on an empty box.
65
+ */
66
+ _openEditor(current, e) {
67
+ this._closeEditor();
68
+ const input = document.createElement('input');
69
+ input.value = current;
70
+ Object.assign(input.style, {
71
+ position: 'fixed',
72
+ left: `${e.raw.clientX}px`,
73
+ top: `${e.raw.clientY}px`,
74
+ transform: 'translate(-50%, -50%)',
75
+ zIndex: '10000',
76
+ minWidth: '140px',
77
+ padding: '3px 8px',
78
+ font: '14px Inter, sans-serif',
79
+ color: 'var(--affine-text-primary-color, #1f2328)',
80
+ background: 'var(--affine-background-overlay-panel-color, #ffffff)',
81
+ border: '1px solid var(--affine-primary-color, #1e96eb)',
82
+ borderRadius: '6px',
83
+ boxShadow: 'var(--affine-shadow-2, 0 2px 8px rgba(0,0,0,0.18))',
84
+ outline: 'none',
85
+ });
86
+ document.body.append(input);
87
+ this._editor = input;
88
+ // Mark "editing" so the global edgeless key handlers (delete, escape, …)
89
+ // don't act on the frame while the user types.
90
+ this.gfx.selection.set({ elements: [this.model.id], editing: true });
91
+ input.focus();
92
+ input.select();
93
+ const commit = () => {
94
+ // Guard against re-entrancy: removing the input fires `blur`, which would
95
+ // otherwise call `commit` a second time.
96
+ if (this._editor !== input)
97
+ return;
98
+ const value = input.value;
99
+ this._closeEditor();
100
+ // Opening an editor is not renaming: an untouched value would push an
101
+ // empty entry onto undo and freeze the drawn wording as the user's own.
102
+ if (value === current)
103
+ return;
104
+ this.gfx.std.store.captureSync();
105
+ this.gfx.std
106
+ .get(EdgelessCRUDIdentifier)
107
+ .updateElement(this.model.id, { name: value });
108
+ };
109
+ input.addEventListener('keydown', ev => {
110
+ ev.stopPropagation();
111
+ if (ev.key === 'Enter') {
112
+ ev.preventDefault();
113
+ commit();
114
+ }
115
+ else if (ev.key === 'Escape') {
116
+ ev.preventDefault();
117
+ this._closeEditor();
118
+ }
119
+ });
120
+ input.addEventListener('blur', commit);
121
+ }
122
+ _closeEditor() {
123
+ if (!this._editor)
124
+ return;
125
+ const input = this._editor;
126
+ this._editor = null;
127
+ input.remove();
128
+ if (this.isConnected) {
129
+ this.gfx.selection.set({ elements: [this.model.id], editing: false });
130
+ }
131
+ }
132
+ }
133
+ /** The sheet a C4 diagram is drawn on. Double-click its title to rename it. */
134
+ export class C4BoardView extends C4FrameView {
135
+ static { this.type = 'c4Board'; }
136
+ get def() {
137
+ return C4_BOARD_BACKGROUND;
138
+ }
139
+ }
140
+ /** The dashed frame drawn round part of one. Double-click its name to rename. */
141
+ export class C4BoundaryView extends C4FrameView {
142
+ static { this.type = 'c4Boundary'; }
143
+ get def() {
144
+ return C4_BOUNDARY_BACKGROUND;
145
+ }
146
+ }