@formicoidea/labre-ddd-shared 0.59.0 → 0.61.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.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@labre/affine-gfx-ddd-shared/headless` — the DDD tables and the pure
3
+ * element builders, with no editor behind them (`docs/adr/0045`): what a
4
+ * board's `./interchange` toolbox builds a sticky, a bubble, a dot, a marker,
5
+ * a cloud or a relationship from. The package index re-exports every one of
6
+ * these unchanged.
7
+ *
8
+ * Held by `interchange-headless.unit.spec.ts` in `packages/affine/all`: add
9
+ * only from files that import nothing but the headless entries of core
10
+ * (`@labre/global`, `@labre/affine-model/headless`,
11
+ * `@labre/affine-shared/headless`, `@labre/affine-block-surface/interchange`).
12
+ */
13
+ export * from './shared/consts.js';
14
+ export * from './shared/elements.js';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@labre/affine-gfx-ddd-shared/headless` — the DDD tables and the pure
3
+ * element builders, with no editor behind them (`docs/adr/0045`): what a
4
+ * board's `./interchange` toolbox builds a sticky, a bubble, a dot, a marker,
5
+ * a cloud or a relationship from. The package index re-exports every one of
6
+ * these unchanged.
7
+ *
8
+ * Held by `interchange-headless.unit.spec.ts` in `packages/affine/all`: add
9
+ * only from files that import nothing but the headless entries of core
10
+ * (`@labre/global`, `@labre/affine-model/headless`,
11
+ * `@labre/affine-shared/headless`, `@labre/affine-block-surface/interchange`).
12
+ */
13
+ export * from './shared/consts.js';
14
+ export * from './shared/elements.js';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './shared/consts.js';
2
+ export * from './shared/elements.js';
2
3
  export * from './shared/prefabs.js';
3
4
  export * from './shared/utils.js';
4
5
  export * from './toolbar/icons.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './shared/consts.js';
2
+ export * from './shared/elements.js';
2
3
  export * from './shared/prefabs.js';
3
4
  export * from './shared/utils.js';
4
5
  export * from './toolbar/icons.js';
@@ -18,6 +18,12 @@
18
18
  * second key here for the same English word would only be the drift the
19
19
  * manifest guard exists to catch.
20
20
  */
21
+ /**
22
+ * The boards' chrome font. Declared here rather than in `utils.ts`, which
23
+ * re-exports the editor's `refScale`, so the editor-free entry (`headless.ts`)
24
+ * can carry it to a board's declaration.
25
+ */
26
+ export declare const FONT_FAMILY = "Inter, sans-serif";
21
27
  /** Square sticky side (px, canvas reference). */
22
28
  export declare const STICKY_SIZE = 120;
23
29
  /** Faux drop-shadow offset + colour (a plain offset rect behind the sticky). */
@@ -229,4 +235,4 @@ export declare const MOVEMENT_COLOR = "#ff3333";
229
235
  * three values — one declaration, so a DDD caption and the legend line that
230
236
  * explains it cannot drift apart.
231
237
  */
232
- export { LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, } from '@formicoidea/labre-core/blocks/surface';
238
+ export { LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, } from '@formicoidea/labre-core/interchange';
@@ -1,4 +1,4 @@
1
- import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/consts';
1
+ import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/headless';
2
2
  /**
3
3
  * Shared DDD palette and sizes. Every tool composes the same irreducible units
4
4
  * (sticky, dot, bubble, connector, label) from these presets — no duplicated
@@ -19,6 +19,12 @@ import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/consts';
19
19
  * second key here for the same English word would only be the drift the
20
20
  * manifest guard exists to catch.
21
21
  */
22
+ /**
23
+ * The boards' chrome font. Declared here rather than in `utils.ts`, which
24
+ * re-exports the editor's `refScale`, so the editor-free entry (`headless.ts`)
25
+ * can carry it to a board's declaration.
26
+ */
27
+ export const FONT_FAMILY = 'Inter, sans-serif';
22
28
  /** Square sticky side (px, canvas reference). */
23
29
  export const STICKY_SIZE = 120;
24
30
  /** Faux drop-shadow offset + colour (a plain offset rect behind the sticky). */
@@ -221,4 +227,4 @@ export const MOVEMENT_COLOR = '#ff3333';
221
227
  * three values — one declaration, so a DDD caption and the legend line that
222
228
  * explains it cannot drift apart.
223
229
  */
224
- export { LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, } from '@formicoidea/labre-core/blocks/surface';
230
+ export { LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, } from '@formicoidea/labre-core/interchange';
@@ -0,0 +1,109 @@
1
+ import type { SerializedElementProps, ToolboxPoint } from '@formicoidea/labre-core/interchange';
2
+ import { TextFitMode } from '@formicoidea/labre-core/model/headless';
3
+ export interface ShapeOpts {
4
+ shapeType?: 'rect' | 'ellipse' | 'diamond';
5
+ fill: string;
6
+ /**
7
+ * Semantic role (`<framework>:<role>`) stamped on the shape, so a validation
8
+ * rule can recognise it. `undefined` writes NOTHING — no `role` key is
9
+ * persisted and the artefact stays a neutral drawing, which is what every
10
+ * caller that does not pass one keeps getting and what every DDD artefact
11
+ * created before this parameter existed already is.
12
+ */
13
+ role?: string;
14
+ stroke?: string;
15
+ strokeWidth?: number;
16
+ radius?: number;
17
+ /**
18
+ * Shape-owned centred label. The text belongs to the shape itself (edited
19
+ * by double-click, moves/resizes with it) with the given text-fit mode —
20
+ * `contained` mimics a physical post-it.
21
+ */
22
+ label?: {
23
+ text: string;
24
+ color: string;
25
+ fontFamily: string;
26
+ fontSize: number;
27
+ fit: TextFitMode;
28
+ };
29
+ }
30
+ /**
31
+ * The props a DDD shape is CREATED with, its label's text as a string. The
32
+ * prefabs' `dddShapeProps` is this with the text as a `Y.Text`.
33
+ */
34
+ export declare function dddShapeElement(x: number, y: number, w: number, h: number, opts: ShapeOpts): SerializedElementProps;
35
+ /**
36
+ * What a Core Domain DOT is, as shape options — read by the builder below and
37
+ * by the morph, so the two can never disagree about what a platform
38
+ * sub-domain looks like.
39
+ */
40
+ export declare function dotShapeOpts(fill: string, role?: string): ShapeOpts;
41
+ /** What a Team Topologies MARKER square is, the same way. */
42
+ export declare function markerShapeOpts(fill: string, role?: string): ShapeOpts;
43
+ export interface StickyOpts {
44
+ fill: string;
45
+ text: string;
46
+ label: string;
47
+ shapeType?: 'rect' | 'diamond';
48
+ size?: number;
49
+ /**
50
+ * Stamped on the FACE, never on the shadow: the face is the artefact — it
51
+ * is what carries the words, what a connector attaches to and what the
52
+ * grammar rules read. The shadow is ink.
53
+ */
54
+ role?: string;
55
+ }
56
+ /**
57
+ * A post-it whose face is at `at`: the faux-shadow rect, the coloured face
58
+ * whose label is its OWN shape text in `contained` fit mode, and their group.
59
+ */
60
+ export declare function stickyElements(at: ToolboxPoint, opts: StickyOpts, local?: string): SerializedElementProps[];
61
+ /**
62
+ * A Context Map bounded-context bubble at `at`: a rounded pill whose label is
63
+ * the pill's own shape text in `overflow` fit mode (fixed pill, fixed font — a
64
+ * long context name may spill out rather than deform the map).
65
+ */
66
+ export declare function bubbleElement(at: ToolboxPoint, label: string, role?: string, local?: string): SerializedElementProps;
67
+ /**
68
+ * A Core Domain dot at `at`; with a label, the caption to its right and the
69
+ * group of the two. The role rides on the ELLIPSE, never on the group.
70
+ */
71
+ export declare function dotElements(at: ToolboxPoint, fill: string, label?: string, role?: string, local?: string): SerializedElementProps[];
72
+ export interface MarkerOpts {
73
+ fill: string;
74
+ letter: string;
75
+ label?: string;
76
+ role?: string;
77
+ }
78
+ /**
79
+ * A Team Topologies interaction-mode marker whose square is at `at`: the
80
+ * square, its centred letter, the caption beside it when given, and the group.
81
+ * The role rides on the SQUARE: the letter is its glyph and the word beside it
82
+ * is a caption.
83
+ */
84
+ export declare function markerElements(at: ToolboxPoint, opts: MarkerOpts, local?: string): SerializedElementProps[];
85
+ /**
86
+ * A cloud (Big Ball of Mud / general-purpose boundary) at `at`: a dense
87
+ * closed polygon; with a label, the text across it and the group of the two.
88
+ * The role rides on the POLYGON, never on the group: the polygon is what a
89
+ * relationship binds to and what the legend scan meets.
90
+ */
91
+ export declare function cloudElements(at: ToolboxPoint, label?: string, role?: string, local?: string): SerializedElementProps[];
92
+ /** A connector end: an element (or local) id, or a free point. */
93
+ export type DddConnectorEnd = {
94
+ id: string;
95
+ } | {
96
+ position: [number, number];
97
+ };
98
+ export interface DddConnectorOpts {
99
+ rearArrow?: boolean;
100
+ dashed?: boolean;
101
+ stroke?: string;
102
+ strokeWidth?: number;
103
+ /** Edge role; `undefined` leaves the connector a neutral line. */
104
+ role?: string;
105
+ /** The words riding on the line; none when absent or blank. */
106
+ label?: string;
107
+ }
108
+ /** A relationship connector (the single connector unit reused for all patterns). */
109
+ export declare function dddConnectorElement(source: DddConnectorEnd, target: DddConnectorEnd, opts?: DddConnectorOpts): SerializedElementProps;
@@ -0,0 +1,240 @@
1
+ import { ConnectorMode, FontWeight, PointStyle, ShapeStyle, StrokeStyle, TextFitMode, } from '@formicoidea/labre-core/model/headless';
2
+ import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/headless';
3
+ import { Bound } from '@formicoidea/labre-core/global/gfx';
4
+ import { CLOUD, CLOUD_VERTICES, CM_BUBBLE, DOT_SIZE, LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, MARKER_SIZE, SHADOW_COLOR, SHADOW_OFFSET, STICKY_FONT, STICKY_FONT_SIZE, STICKY_RADIUS, STICKY_SIZE, } from './consts.js';
5
+ /**
6
+ * The DDD artefacts as PURE builders (`docs/adr/0045`): the elements a
7
+ * sticky, a bubble, a dot, a marker, a cloud or a relationship is made of, as
8
+ * serialized props, with no editor behind them. The prefabs (`prefabs.ts`)
9
+ * write these through `surface.addElement`, and each board's toolbox (its
10
+ * `./interchange` entry) hands them to a host with no editor — one assembly
11
+ * per artefact, shared by the three boards, which is what this package is for.
12
+ *
13
+ * The contract is a reader's (ADR 0012 D3): painting order, LOCAL ids, each
14
+ * composite's members before the group that holds them, the group last. `at`
15
+ * is the top-left of the artefact's own box (the sticky's face, the dot, the
16
+ * marker's square); a caption written beside it lies outside that box.
17
+ *
18
+ * A builder writes the text it is given and never translates it: the seed in
19
+ * the host's language is resolved by the editor's creation site (ADR 0023).
20
+ * A shape's text is a plain string here; the shape model turns it into a
21
+ * `Y.Text` on `addElement` (`ShapeElementModel.propsToY`), so the stored
22
+ * element is the same whichever side built it.
23
+ */
24
+ const NO_STROKE = '#00000000';
25
+ /** The ink every Core Domain artefact is outlined with: the shared scale's. */
26
+ const ARTEFACT_STROKE = NOTATION_NEUTRALS.ink;
27
+ /**
28
+ * The props a DDD shape is CREATED with, its label's text as a string. The
29
+ * prefabs' `dddShapeProps` is this with the text as a `Y.Text`.
30
+ */
31
+ export function dddShapeElement(x, y, w, h, opts) {
32
+ const { shapeType = 'rect', fill, stroke = NO_STROKE, strokeWidth = 0, radius = 0, label, role, } = opts;
33
+ return {
34
+ type: 'shape',
35
+ // `undefined` writes nothing: a neutral artefact keeps no `role` key.
36
+ role,
37
+ shapeType,
38
+ filled: true,
39
+ fillColor: fill,
40
+ strokeColor: stroke,
41
+ strokeWidth,
42
+ shapeStyle: ShapeStyle.General,
43
+ roughness: 0,
44
+ radius,
45
+ xywh: new Bound(x, y, w, h).serialize(),
46
+ ...(label
47
+ ? {
48
+ text: label.text,
49
+ color: label.color,
50
+ fontFamily: label.fontFamily,
51
+ fontSize: label.fontSize,
52
+ textAlign: 'center',
53
+ textFitMode: label.fit,
54
+ }
55
+ : {}),
56
+ };
57
+ }
58
+ /** A canvas TEXT element: a caption beside an artefact, or a marker's letter. */
59
+ function textElement(id, x, y, w, text, color, fontFamily, fontSize, textAlign = 'center') {
60
+ return {
61
+ type: 'text',
62
+ id,
63
+ text,
64
+ color,
65
+ fontFamily,
66
+ fontSize,
67
+ textAlign,
68
+ fontWeight: FontWeight.Regular,
69
+ xywh: new Bound(x, y, w, fontSize + 10).serialize(),
70
+ };
71
+ }
72
+ /** The group holding `members`, written after them. */
73
+ function groupOf(id, members) {
74
+ return {
75
+ type: 'group',
76
+ id,
77
+ children: Object.fromEntries(members.map(m => [m.id, true])),
78
+ };
79
+ }
80
+ /**
81
+ * What a Core Domain DOT is, as shape options — read by the builder below and
82
+ * by the morph, so the two can never disagree about what a platform
83
+ * sub-domain looks like.
84
+ */
85
+ export function dotShapeOpts(fill, role) {
86
+ return {
87
+ shapeType: 'ellipse',
88
+ fill,
89
+ stroke: ARTEFACT_STROKE,
90
+ strokeWidth: 1.5,
91
+ role,
92
+ };
93
+ }
94
+ /** What a Team Topologies MARKER square is, the same way. */
95
+ export function markerShapeOpts(fill, role) {
96
+ return {
97
+ fill,
98
+ stroke: ARTEFACT_STROKE,
99
+ strokeWidth: 1.5,
100
+ radius: 4,
101
+ role,
102
+ };
103
+ }
104
+ /**
105
+ * A post-it whose face is at `at`: the faux-shadow rect, the coloured face
106
+ * whose label is its OWN shape text in `contained` fit mode, and their group.
107
+ */
108
+ export function stickyElements(at, opts, local = 'sticky') {
109
+ const { fill, text, label, shapeType = 'rect', size = STICKY_SIZE, role, } = opts;
110
+ const radius = shapeType === 'rect' ? STICKY_RADIUS : 0;
111
+ const members = [
112
+ {
113
+ ...dddShapeElement(at.x + SHADOW_OFFSET, at.y + SHADOW_OFFSET, size, size, { shapeType, fill: SHADOW_COLOR, radius }),
114
+ id: `${local}:shadow`,
115
+ },
116
+ {
117
+ ...dddShapeElement(at.x, at.y, size, size, {
118
+ shapeType,
119
+ fill,
120
+ radius,
121
+ role,
122
+ label: {
123
+ text: label,
124
+ color: text,
125
+ fontFamily: STICKY_FONT,
126
+ fontSize: STICKY_FONT_SIZE,
127
+ fit: TextFitMode.Contained,
128
+ },
129
+ }),
130
+ id: `${local}:face`,
131
+ },
132
+ ];
133
+ return [...members, groupOf(`${local}:group`, members)];
134
+ }
135
+ /**
136
+ * A Context Map bounded-context bubble at `at`: a rounded pill whose label is
137
+ * the pill's own shape text in `overflow` fit mode (fixed pill, fixed font — a
138
+ * long context name may spill out rather than deform the map).
139
+ */
140
+ export function bubbleElement(at, label, role, local = 'bubble') {
141
+ const { w, h, radius, fill, stroke, text } = CM_BUBBLE;
142
+ return {
143
+ ...dddShapeElement(at.x, at.y, w, h, {
144
+ fill,
145
+ stroke,
146
+ strokeWidth: 1.5,
147
+ radius,
148
+ role,
149
+ label: {
150
+ text: label,
151
+ color: text,
152
+ fontFamily: LABEL_FONT,
153
+ fontSize: LABEL_FONT_SIZE,
154
+ fit: TextFitMode.Overflow,
155
+ },
156
+ }),
157
+ id: local,
158
+ };
159
+ }
160
+ /**
161
+ * A Core Domain dot at `at`; with a label, the caption to its right and the
162
+ * group of the two. The role rides on the ELLIPSE, never on the group.
163
+ */
164
+ export function dotElements(at, fill, label, role, local = 'dot') {
165
+ const d = DOT_SIZE;
166
+ const dot = {
167
+ ...dddShapeElement(at.x, at.y, d, d, dotShapeOpts(fill, role)),
168
+ id: `${local}:dot`,
169
+ };
170
+ if (!label)
171
+ return [dot];
172
+ const caption = textElement(`${local}:caption`, at.x + d + 6, at.y + d / 2 - LABEL_FONT_SIZE / 2, 170, label, LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, 'left');
173
+ return [dot, caption, groupOf(`${local}:group`, [dot, caption])];
174
+ }
175
+ /**
176
+ * A Team Topologies interaction-mode marker whose square is at `at`: the
177
+ * square, its centred letter, the caption beside it when given, and the group.
178
+ * The role rides on the SQUARE: the letter is its glyph and the word beside it
179
+ * is a caption.
180
+ */
181
+ export function markerElements(at, opts, local = 'marker') {
182
+ const { fill, letter, label, role } = opts;
183
+ const s = MARKER_SIZE;
184
+ const members = [
185
+ {
186
+ ...dddShapeElement(at.x, at.y, s, s, markerShapeOpts(fill, role)),
187
+ id: `${local}:square`,
188
+ },
189
+ textElement(`${local}:letter`, at.x, at.y + s / 2 - 9, s, letter, NOTATION_NEUTRALS.ink, LABEL_FONT, 15),
190
+ ];
191
+ if (label) {
192
+ members.push(textElement(`${local}:caption`, at.x + s + 6, at.y + s / 2 - LABEL_FONT_SIZE / 2, 150, label, LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, 'left'));
193
+ }
194
+ return [...members, groupOf(`${local}:group`, members)];
195
+ }
196
+ /**
197
+ * A cloud (Big Ball of Mud / general-purpose boundary) at `at`: a dense
198
+ * closed polygon; with a label, the text across it and the group of the two.
199
+ * The role rides on the POLYGON, never on the group: the polygon is what a
200
+ * relationship binds to and what the legend scan meets.
201
+ */
202
+ export function cloudElements(at, label, role, local = 'cloud') {
203
+ const { w, h, fill, stroke } = CLOUD;
204
+ const cloud = {
205
+ type: 'shape',
206
+ id: `${local}:cloud`,
207
+ shapeType: 'polygon',
208
+ vertices: CLOUD_VERTICES,
209
+ isClosed: true,
210
+ filled: true,
211
+ fillColor: fill,
212
+ strokeColor: stroke,
213
+ strokeWidth: 1.5,
214
+ shapeStyle: ShapeStyle.General,
215
+ roughness: 0,
216
+ xywh: new Bound(at.x, at.y, w, h).serialize(),
217
+ ...(role ? { role } : {}),
218
+ };
219
+ if (!label)
220
+ return [cloud];
221
+ const caption = textElement(`${local}:caption`, at.x + 12, at.y + h / 2 - LABEL_FONT_SIZE / 2, w - 24, label, NOTATION_NEUTRALS.ink, LABEL_FONT, LABEL_FONT_SIZE);
222
+ return [cloud, caption, groupOf(`${local}:group`, [cloud, caption])];
223
+ }
224
+ /** A relationship connector (the single connector unit reused for all patterns). */
225
+ export function dddConnectorElement(source, target, opts = {}) {
226
+ const { rearArrow = false, dashed = false, stroke = LABEL_COLOR, strokeWidth = 2, role, label, } = opts;
227
+ return {
228
+ type: 'connector',
229
+ role,
230
+ mode: ConnectorMode.Straight,
231
+ stroke,
232
+ strokeWidth,
233
+ strokeStyle: dashed ? StrokeStyle.Dash : StrokeStyle.Solid,
234
+ frontEndpointStyle: PointStyle.None,
235
+ rearEndpointStyle: rearArrow ? PointStyle.Arrow : PointStyle.None,
236
+ source,
237
+ target,
238
+ ...(label?.trim() ? { text: label.trim() } : {}),
239
+ };
240
+ }
@@ -1,11 +1,18 @@
1
- import { TextFitMode } from '@formicoidea/labre-core/model';
2
1
  import type { BlockStdScope } from '@formicoidea/labre-core/std';
3
2
  import { type GfxController } from '@formicoidea/labre-core/std/gfx';
3
+ import { type ShapeOpts } from './elements.js';
4
4
  /**
5
5
  * Irreducible reusable units composed by all three DDD sub-menus. Each helper
6
- * wraps native `surface.addElement` (shape / text / connector / group); nothing
7
- * here defines a new element type, so the same builders back Event Storming
8
- * stickies, Context Map bubbles + relationships and Core Domain dots + arrows.
6
+ * writes the elements its pure builder returns (`elements.ts`,
7
+ * `docs/adr/0045`) through native `surface.addElement` (shape / text /
8
+ * connector) and the group command; nothing here defines a new element type,
9
+ * so the same builders back Event Storming stickies, Context Map bubbles +
10
+ * relationships and Core Domain dots + arrows, here and on a host with no
11
+ * editor.
12
+ *
13
+ * What stays here is what only an editor has: the viewport centre the
14
+ * prefabs are placed around, and the group command, which names the group
15
+ * ("Group N", resolved in the host's language) and stamps its user layer.
9
16
  */
10
17
  type Surface = NonNullable<GfxController['surface']>;
11
18
  /**
@@ -18,36 +25,9 @@ type Surface = NonNullable<GfxController['surface']>;
18
25
  export declare function placeDddElement(std: BlockStdScope, build: (surface: Surface, cx: number, cy: number) => string): void;
19
26
  /** Group several element ids; returns the group id (or the first id on failure). */
20
27
  export declare function groupIds(std: BlockStdScope, ids: string[]): string;
21
- export interface ShapeOpts {
22
- shapeType?: 'rect' | 'ellipse' | 'diamond';
23
- fill: string;
24
- /**
25
- * Semantic role (`<framework>:<role>`) stamped on the shape, so a validation
26
- * rule can recognise it. `undefined` writes NOTHING — no `role` key is
27
- * persisted and the artefact stays a neutral drawing, which is what every
28
- * caller that does not pass one keeps getting and what every DDD artefact
29
- * created before this parameter existed already is.
30
- */
31
- role?: string;
32
- stroke?: string;
33
- strokeWidth?: number;
34
- radius?: number;
35
- /**
36
- * Shape-owned centred label. The text belongs to the shape itself (edited
37
- * by double-click, moves/resizes with it) with the given text-fit mode —
38
- * `contained` mimics a physical post-it.
39
- */
40
- label?: {
41
- text: string;
42
- color: string;
43
- fontFamily: string;
44
- fontSize: number;
45
- fit: TextFitMode;
46
- };
47
- }
48
28
  /**
49
- * The props a DDD shape is CREATED with, as a record — the write of
50
- * {@link addShape}, minus the write.
29
+ * The props a DDD shape is CREATED with, as a record — the write of the
30
+ * prefabs, minus the write.
51
31
  *
52
32
  * Split out for the Core Domain morph, and for the reason C4's `presets.ts`
53
33
  * gives: a kind's appearance is written by the preset of the kind it was created
@@ -85,14 +65,6 @@ export declare function addSticky(surface: Surface, std: BlockStdScope, cx: numb
85
65
  * long context name may spill out rather than deform the map).
86
66
  */
87
67
  export declare function addBubble(surface: Surface, cx: number, cy: number, label: string, role?: string): string;
88
- /**
89
- * What a Core Domain DOT is, as shape options — read by the creation site below
90
- * and by the morph, so the two can never disagree about what a platform
91
- * sub-domain looks like. See {@link dddShapeProps}.
92
- */
93
- export declare function dotShapeOpts(fill: string, role?: string): ShapeOpts;
94
- /** What a Team Topologies MARKER square is, the same way. */
95
- export declare function markerShapeOpts(fill: string, role?: string): ShapeOpts;
96
68
  /** A Core Domain dot (sub-domain / bounded context); optional label to its right. */
97
69
  export declare function addDot(surface: Surface, std: BlockStdScope, cx: number, cy: number, fill: string, label?: string,
98
70
  /** Stamped on the ELLIPSE, never on the group: the dot is the artefact. */
@@ -1,12 +1,9 @@
1
- import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
1
+ import { DefaultTool, } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
3
- import { ConnectorMode, FontWeight, PointStyle, ShapeStyle, StrokeStyle, TextFitMode, } from '@formicoidea/labre-core/model';
4
- import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/consts';
5
- import { Bound } from '@formicoidea/labre-core/global/gfx';
6
3
  import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
7
4
  import * as Y from 'yjs';
8
- import { CLOUD, CLOUD_VERTICES, CM_BUBBLE, DOT_SIZE, LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, MARKER_SIZE, SHADOW_COLOR, SHADOW_OFFSET, STICKY_FONT, STICKY_FONT_SIZE, STICKY_RADIUS, STICKY_SIZE, } from './consts.js';
9
- const NO_STROKE = '#00000000';
5
+ import { CLOUD, CM_BUBBLE, DOT_SIZE, MARKER_SIZE, STICKY_SIZE, } from './consts.js';
6
+ import { bubbleElement, cloudElements, dddConnectorElement, dddShapeElement, dotElements, markerElements, stickyElements, } from './elements.js';
10
7
  /**
11
8
  * Build one DDD artefact at the viewport centre and select it, leaving the
12
9
  * palette open so several can be added in a row. This is the old
@@ -31,8 +28,41 @@ export function groupIds(std, ids) {
31
28
  return result.groupId || ids[0];
32
29
  }
33
30
  /**
34
- * The props a DDD shape is CREATED with, as a record — the write of
35
- * {@link addShape}, minus the write.
31
+ * A shape's label as the `Y.Text` the prefabs have always written. The shape
32
+ * model's `propsToY` makes the same `Y.Text` of a string, so this changes
33
+ * nothing stored; it keeps what `addElement` receives from a prefab what it
34
+ * was before the builders existed.
35
+ */
36
+ function withYText(props) {
37
+ return props.type === 'shape' && typeof props.text === 'string'
38
+ ? { ...props, text: new Y.Text(props.text) }
39
+ : props;
40
+ }
41
+ /**
42
+ * Write a builder's elements in order, minting real ids for the local ones;
43
+ * a group goes through the group command with its members' minted ids.
44
+ * Returns the id of the last element: the group of a composite, the element
45
+ * itself otherwise.
46
+ */
47
+ function addElements(surface, std, elements) {
48
+ const minted = new Map();
49
+ let last = '';
50
+ for (const { id: local, ...props } of elements) {
51
+ if (props.type === 'group' && std) {
52
+ const children = Object.keys(props.children);
53
+ last = groupIds(std, children.map(child => minted.get(child) ?? child));
54
+ }
55
+ else {
56
+ last = surface.addElement(withYText(props));
57
+ }
58
+ if (typeof local === 'string')
59
+ minted.set(local, last);
60
+ }
61
+ return last;
62
+ }
63
+ /**
64
+ * The props a DDD shape is CREATED with, as a record — the write of the
65
+ * prefabs, minus the write.
36
66
  *
37
67
  * Split out for the Core Domain morph, and for the reason C4's `presets.ts`
38
68
  * gives: a kind's appearance is written by the preset of the kind it was created
@@ -42,46 +72,7 @@ export function groupIds(std, ids) {
42
72
  * element.
43
73
  */
44
74
  export function dddShapeProps(x, y, w, h, opts) {
45
- const { shapeType = 'rect', fill, stroke = NO_STROKE, strokeWidth = 0, radius = 0, label, role, } = opts;
46
- return {
47
- type: 'shape',
48
- // `undefined` writes nothing: a neutral artefact keeps no `role` key.
49
- role,
50
- shapeType,
51
- filled: true,
52
- fillColor: fill,
53
- strokeColor: stroke,
54
- strokeWidth,
55
- shapeStyle: ShapeStyle.General,
56
- roughness: 0,
57
- radius,
58
- xywh: new Bound(x, y, w, h).serialize(),
59
- ...(label
60
- ? {
61
- text: new Y.Text(label.text),
62
- color: label.color,
63
- fontFamily: label.fontFamily,
64
- fontSize: label.fontSize,
65
- textAlign: 'center',
66
- textFitMode: label.fit,
67
- }
68
- : {}),
69
- };
70
- }
71
- function addShape(surface, x, y, w, h, opts) {
72
- return surface.addElement(dddShapeProps(x, y, w, h, opts));
73
- }
74
- function addText(surface, x, y, w, text, color, fontFamily, fontSize, textAlign = 'center', bold = false) {
75
- return surface.addElement({
76
- type: 'text',
77
- text,
78
- color,
79
- fontFamily,
80
- fontSize,
81
- textAlign,
82
- fontWeight: bold ? FontWeight.SemiBold : FontWeight.Regular,
83
- xywh: new Bound(x, y, w, fontSize + 10).serialize(),
84
- });
75
+ return withYText(dddShapeElement(x, y, w, h, opts));
85
76
  }
86
77
  /**
87
78
  * A post-it: faux-shadow rect + coloured face whose label is the face's OWN
@@ -91,28 +82,8 @@ function addText(surface, x, y, w, text, color, fontFamily, fontSize, textAlign
91
82
  * text element; they keep working untouched.)
92
83
  */
93
84
  export function addSticky(surface, std, cx, cy, opts) {
94
- const { fill, text, label, shapeType = 'rect', size = STICKY_SIZE, role, } = opts;
95
- const half = size / 2;
96
- const radius = shapeType === 'rect' ? STICKY_RADIUS : 0;
97
- const shadow = addShape(surface, cx - half + SHADOW_OFFSET, cy - half + SHADOW_OFFSET, size, size, {
98
- shapeType,
99
- fill: SHADOW_COLOR,
100
- radius,
101
- });
102
- const face = addShape(surface, cx - half, cy - half, size, size, {
103
- shapeType,
104
- fill,
105
- radius,
106
- role,
107
- label: {
108
- text: label,
109
- color: text,
110
- fontFamily: STICKY_FONT,
111
- fontSize: STICKY_FONT_SIZE,
112
- fit: TextFitMode.Contained,
113
- },
114
- });
115
- return groupIds(std, [shadow, face]);
85
+ const half = (opts.size ?? STICKY_SIZE) / 2;
86
+ return addElements(surface, std, stickyElements({ x: cx - half, y: cy - half }, opts));
116
87
  }
117
88
  /**
118
89
  * A Context Map bounded-context bubble: a rounded pill whose label is the
@@ -120,74 +91,21 @@ export function addSticky(surface, std, cx, cy, opts) {
120
91
  * long context name may spill out rather than deform the map).
121
92
  */
122
93
  export function addBubble(surface, cx, cy, label, role) {
123
- const { w, h, radius, fill, stroke, text } = CM_BUBBLE;
124
- return addShape(surface, cx - w / 2, cy - h / 2, w, h, {
125
- fill,
126
- stroke,
127
- strokeWidth: 1.5,
128
- radius,
129
- role,
130
- label: {
131
- text: label,
132
- color: text,
133
- fontFamily: LABEL_FONT,
134
- fontSize: LABEL_FONT_SIZE,
135
- fit: TextFitMode.Overflow,
136
- },
137
- });
138
- }
139
- /** The ink every Core Domain artefact is outlined with: the shared scale's. */
140
- const ARTEFACT_STROKE = NOTATION_NEUTRALS.ink;
141
- /**
142
- * What a Core Domain DOT is, as shape options — read by the creation site below
143
- * and by the morph, so the two can never disagree about what a platform
144
- * sub-domain looks like. See {@link dddShapeProps}.
145
- */
146
- export function dotShapeOpts(fill, role) {
147
- return {
148
- shapeType: 'ellipse',
149
- fill,
150
- stroke: ARTEFACT_STROKE,
151
- strokeWidth: 1.5,
152
- role,
153
- };
154
- }
155
- /** What a Team Topologies MARKER square is, the same way. */
156
- export function markerShapeOpts(fill, role) {
157
- return {
158
- fill,
159
- stroke: ARTEFACT_STROKE,
160
- strokeWidth: 1.5,
161
- radius: 4,
162
- role,
163
- };
94
+ const { w, h } = CM_BUBBLE;
95
+ return addElements(surface, undefined, [
96
+ bubbleElement({ x: cx - w / 2, y: cy - h / 2 }, label, role),
97
+ ]);
164
98
  }
165
99
  /** A Core Domain dot (sub-domain / bounded context); optional label to its right. */
166
100
  export function addDot(surface, std, cx, cy, fill, label,
167
101
  /** Stamped on the ELLIPSE, never on the group: the dot is the artefact. */
168
102
  role) {
169
103
  const d = DOT_SIZE;
170
- const dot = addShape(surface, cx - d / 2, cy - d / 2, d, d, dotShapeOpts(fill, role));
171
- if (!label)
172
- return dot;
173
- const lbl = addText(surface, cx + d / 2 + 6, cy - LABEL_FONT_SIZE / 2, 170, label, LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, 'left');
174
- return groupIds(std, [dot, lbl]);
104
+ return addElements(surface, std, dotElements({ x: cx - d / 2, y: cy - d / 2 }, fill, label, role));
175
105
  }
176
106
  /** A relationship connector (the single connector unit reused for all patterns). */
177
107
  export function addConnector(surface, x1, y1, x2, y2, opts = {}) {
178
- const { rearArrow = false, dashed = false, stroke = LABEL_COLOR, strokeWidth = 2, role, } = opts;
179
- return surface.addElement({
180
- type: 'connector',
181
- role,
182
- mode: ConnectorMode.Straight,
183
- stroke,
184
- strokeWidth,
185
- strokeStyle: dashed ? StrokeStyle.Dash : StrokeStyle.Solid,
186
- frontEndpointStyle: PointStyle.None,
187
- rearEndpointStyle: rearArrow ? PointStyle.Arrow : PointStyle.None,
188
- source: { position: [x1, y1] },
189
- target: { position: [x2, y2] },
190
- });
108
+ return surface.addElement(dddConnectorElement({ position: [x1, y1] }, { position: [x2, y2] }, opts));
191
109
  }
192
110
  /**
193
111
  * A Team Topologies interaction-mode marker: a coloured square + centred letter.
@@ -198,37 +116,11 @@ export function addConnector(surface, x1, y1, x2, y2, opts = {}) {
198
116
  * passes none keeps producing the neutral drawing it always did.
199
117
  */
200
118
  export function addMarker(surface, std, cx, cy, opts) {
201
- const { fill, letter, label, role } = opts;
202
119
  const s = MARKER_SIZE;
203
- const box = addShape(surface, cx - s / 2, cy - s / 2, s, s, markerShapeOpts(fill, role));
204
- const glyph = addText(surface, cx - s / 2, cy - 9, s, letter, NOTATION_NEUTRALS.ink, LABEL_FONT, 15);
205
- const ids = [box, glyph];
206
- if (label) {
207
- ids.push(addText(surface, cx + s / 2 + 6, cy - LABEL_FONT_SIZE / 2, 150, label, LABEL_COLOR, LABEL_FONT, LABEL_FONT_SIZE, 'left'));
208
- }
209
- return groupIds(std, ids);
120
+ return addElements(surface, std, markerElements({ x: cx - s / 2, y: cy - s / 2 }, opts));
210
121
  }
211
122
  /** A cloud (Big Ball of Mud / general-purpose boundary): a smoothed closed bezier polygon. */
212
123
  export function addCloud(surface, std, cx, cy, label, role) {
213
- const { w, h, fill, stroke } = CLOUD;
214
- const cloud = surface.addElement({
215
- type: 'shape',
216
- shapeType: 'polygon',
217
- vertices: CLOUD_VERTICES,
218
- isClosed: true,
219
- filled: true,
220
- fillColor: fill,
221
- strokeColor: stroke,
222
- strokeWidth: 1.5,
223
- shapeStyle: ShapeStyle.General,
224
- roughness: 0,
225
- xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
226
- // On the POLYGON, never on the group: the polygon is what a relationship
227
- // binds to and what the legend scan meets.
228
- ...(role ? { role } : {}),
229
- });
230
- if (!label)
231
- return cloud;
232
- const lbl = addText(surface, cx - w / 2 + 12, cy - LABEL_FONT_SIZE / 2, w - 24, label, NOTATION_NEUTRALS.ink, LABEL_FONT, LABEL_FONT_SIZE);
233
- return groupIds(std, [cloud, lbl]);
124
+ const { w, h } = CLOUD;
125
+ return addElements(surface, std, cloudElements({ x: cx - w / 2, y: cy - h / 2 }, label, role));
234
126
  }
@@ -5,4 +5,3 @@
5
5
  * copied here, and in two other packages besides (R37, one source of truth).
6
6
  */
7
7
  export { refScale } from '@formicoidea/labre-core/blocks/surface';
8
- export declare const FONT_FAMILY = "Inter, sans-serif";
@@ -5,4 +5,3 @@
5
5
  * copied here, and in two other packages besides (R37, one source of truth).
6
6
  */
7
7
  export { refScale } from '@formicoidea/labre-core/blocks/surface';
8
- export const FONT_FAMILY = 'Inter, sans-serif';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formicoidea/labre-ddd-shared",
3
3
  "description": "Labre ddd-shared — shared building blocks for @formicoidea/labre-core frameworks.",
4
- "version": "0.59.0",
4
+ "version": "0.61.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",
@@ -13,13 +13,17 @@
13
13
  ".": {
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js"
16
+ },
17
+ "./headless": {
18
+ "types": "./dist/headless.d.ts",
19
+ "import": "./dist/headless.js"
16
20
  }
17
21
  },
18
22
  "files": [
19
23
  "dist"
20
24
  ],
21
25
  "dependencies": {
22
- "@formicoidea/labre-core": "0.59.0",
26
+ "@formicoidea/labre-core": "0.61.0",
23
27
  "lit": "^3.2.0",
24
28
  "yjs": "^13.6.21"
25
29
  }