@formicoidea/labre-framework-edgy 0.31.0 → 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 (48) hide show
  1. package/dist/actions.d.ts +39 -0
  2. package/dist/actions.js +193 -0
  3. package/dist/board-renderer.d.ts +12 -0
  4. package/dist/board-renderer.js +28 -0
  5. package/dist/board-view.d.ts +11 -0
  6. package/dist/board-view.js +20 -0
  7. package/dist/commands.d.ts +4 -0
  8. package/dist/commands.js +103 -0
  9. package/dist/consts.d.ts +35 -0
  10. package/dist/consts.js +46 -0
  11. package/dist/descriptor.d.ts +8 -3
  12. package/dist/descriptor.js +6 -3
  13. package/dist/element-renderer.js +24 -13
  14. package/dist/element-view.js +9 -3
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +2 -1
  17. package/dist/label-layout.js +8 -2
  18. package/dist/legend.d.ts +2 -0
  19. package/dist/legend.js +109 -0
  20. package/dist/metamodel.d.ts +96 -0
  21. package/dist/metamodel.js +128 -0
  22. package/dist/nudges.d.ts +26 -0
  23. package/dist/nudges.js +54 -0
  24. package/dist/profiles.d.ts +2 -0
  25. package/dist/profiles.js +66 -0
  26. package/dist/relation-resolver.d.ts +32 -0
  27. package/dist/relation-resolver.js +101 -0
  28. package/dist/relation.d.ts +142 -0
  29. package/dist/relation.js +188 -0
  30. package/dist/roles.d.ts +108 -0
  31. package/dist/roles.js +182 -0
  32. package/dist/rules.d.ts +43 -0
  33. package/dist/rules.js +153 -0
  34. package/dist/templates/index.d.ts +22 -1
  35. package/dist/templates/index.js +303 -52
  36. package/dist/toolbar/config.d.ts +33 -0
  37. package/dist/toolbar/config.js +82 -15
  38. package/dist/toolbar/edgy-menu.d.ts +7 -25
  39. package/dist/toolbar/edgy-menu.js +7 -186
  40. package/dist/toolbar/edgy-senior-button.js +8 -2
  41. package/dist/toolbar/icons.d.ts +12 -0
  42. package/dist/toolbar/icons.js +31 -0
  43. package/dist/toolbar/senior-tool.js +1 -0
  44. package/dist/translations.d.ts +24 -0
  45. package/dist/translations.js +29 -0
  46. package/dist/view.d.ts +18 -0
  47. package/dist/view.js +95 -8
  48. package/package.json +6 -3
@@ -0,0 +1,39 @@
1
+ import type { BlockStdScope } from '@formicoidea/labre-core/std';
2
+ import { type GfxController } from '@formicoidea/labre-core/std/gfx';
3
+ export type EdgyBoxKind = 'outcome' | 'object' | 'activity';
4
+ /**
5
+ * Create the Enterprise Design Facets diagram centred on the viewport —
6
+ * cropped to the circles + facet labels (no dead margins around the Venn).
7
+ */
8
+ export declare function createEdgyFacets(std: BlockStdScope): void;
9
+ /**
10
+ * Insert the "EDGY dynamic" template (facets background + the 12 elements
11
+ * linked by the 24 verbalised relations), then bring it into view.
12
+ */
13
+ export declare function createEdgyDynamic(std: BlockStdScope): Promise<void>;
14
+ /** Create a blank EDGY board (hover spotlight host) centred on the viewport. */
15
+ export declare function createEdgyBoard(std: BlockStdScope): void;
16
+ /** Create a box base element (outcome / object / activity) with inner text. */
17
+ export declare function createEdgyBox(std: BlockStdScope, kind: EdgyBoxKind): void;
18
+ /**
19
+ * Create the People base element: an (invisible) ellipse decorated with the
20
+ * person glyph by the renderer, plus a native text label below, grouped.
21
+ */
22
+ export declare function createEdgyPeople(std: BlockStdScope): void;
23
+ /**
24
+ * Activate the native connector tool for an EDGY RELATION: pre-styled like the
25
+ * 24 links of the metamodel template, and stamped with the GENERIC role
26
+ * `edgy:relation`.
27
+ *
28
+ * Generic on purpose, and this is the whole design (`./relation.ts`): the verb
29
+ * of an EDGY relation is determined by the ordered pair of elements it runs
30
+ * between — 24 rows, 24 distinct pairs — so there is nothing to ask the user
31
+ * and nothing to pick from a list of twenty-two. The tool arms the parent role,
32
+ * the user drags from the subject to the object, and `EdgyRelationResolver`
33
+ * writes the verb the metamodel gives that pair, as a role and as the label.
34
+ *
35
+ * The style is the template's, to the pixel: straight, `NODE_STROKE`, two units
36
+ * wide, and NO arrowhead at either end — EDGY's reference diagram draws its
37
+ * relations as bare lines and lets the verb say which way the sentence runs.
38
+ */
39
+ export declare function activateEdgyRelation(gfx: GfxController): void;
@@ -0,0 +1,193 @@
1
+ import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
+ import { ConnectorTool } from '@formicoidea/labre-core/gfx/connector';
3
+ import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
4
+ import { createTemplateJob } from '@formicoidea/labre-core/gfx/template';
5
+ import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, } from '@formicoidea/labre-core/model';
6
+ import { Bound } from '@formicoidea/labre-core/global/gfx';
7
+ import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
8
+ import { CROP_LABELED } from './consts.js';
9
+ import { ACTIVITY_VERTICES, INNER_FONT_SIZE, LABEL_FONT_SIZE, LABEL_GAP, NODE_FILL, NODE_LABEL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, OUTCOME_RADIUS, } from './node/consts.js';
10
+ import { EDGY_ROLE } from './roles.js';
11
+ import { edgyDynamicTemplate } from './templates/index.js';
12
+ /** Stroke width the 24 template relations are drawn with. */
13
+ const RELATION_STROKE_WIDTH = 2;
14
+ /** Default facets-diagram size (REF aspect, scaled up so it reads on canvas). */
15
+ const FACETS_SCALE = 1.5;
16
+ /** Default blank-board size. */
17
+ const BOARD_W = 1600;
18
+ const BOARD_H = 1000;
19
+ /** Height of the native People free-text label. */
20
+ const LABEL_H = LABEL_FONT_SIZE + 8;
21
+ const gfxOf = (std) => std.get(GfxControllerIdentifier);
22
+ function finish(gfx, id) {
23
+ gfx.doc.captureSync();
24
+ gfx.tool.setTool(DefaultTool);
25
+ gfx.selection.set({ elements: [id], editing: false });
26
+ // Keep the palette open (native sub-menu behaviour).
27
+ }
28
+ /**
29
+ * Shared props for an EDGY node shape.
30
+ *
31
+ * The `role` is the PERSISTED KIND and nothing more: somebody picking "Object"
32
+ * in the palette has said "this is an object", not "this is a Channel". The
33
+ * twelve official elements are named by the metamodel template, which knows
34
+ * which of them it is drawing; a base element created here specialises
35
+ * `edgy:element` through its kind and is judged by every rule written on the
36
+ * root — the overlap rule — and by none written on a leaf.
37
+ */
38
+ const baseShapeProps = (kind) => ({
39
+ type: 'edgyNode',
40
+ kind,
41
+ role: EDGY_ROLE[kind],
42
+ filled: true,
43
+ fillColor: NODE_FILL,
44
+ strokeColor: NODE_STROKE,
45
+ shapeStyle: ShapeStyle.General,
46
+ roughness: 0,
47
+ });
48
+ /** Add a native free-text label (Inter), used for the People node. */
49
+ function addLabel(surface, text, x, y) {
50
+ return surface.addElement({
51
+ type: 'text',
52
+ text,
53
+ fontFamily: FontFamily.Inter,
54
+ fontSize: LABEL_FONT_SIZE,
55
+ color: NODE_STROKE,
56
+ textAlign: 'center',
57
+ xywh: new Bound(x, y, 120, LABEL_H).serialize(),
58
+ });
59
+ }
60
+ /**
61
+ * Create the Enterprise Design Facets diagram centred on the viewport —
62
+ * cropped to the circles + facet labels (no dead margins around the Venn).
63
+ */
64
+ export function createEdgyFacets(std) {
65
+ const gfx = gfxOf(std);
66
+ if (!gfx.surface)
67
+ return;
68
+ const width = CROP_LABELED.w * FACETS_SCALE;
69
+ const height = CROP_LABELED.h * FACETS_SCALE;
70
+ const { centerX, centerY } = gfx.viewport;
71
+ const id = gfx.surface.addElement({
72
+ type: 'edgy',
73
+ cropToCircles: true,
74
+ // The frame: what makes this an EDGY board rather than three circles, and
75
+ // what a finding is attributed to.
76
+ role: EDGY_ROLE.facets,
77
+ xywh: new Bound(centerX - width / 2, centerY - height / 2, width, height).serialize(),
78
+ });
79
+ finish(gfx, id);
80
+ }
81
+ /**
82
+ * Insert the "EDGY dynamic" template (facets background + the 12 elements
83
+ * linked by the 24 verbalised relations), then bring it into view.
84
+ */
85
+ export async function createEdgyDynamic(std) {
86
+ const gfx = gfxOf(std);
87
+ if (!gfx.surface)
88
+ return;
89
+ const job = createTemplateJob(std, 'template');
90
+ const bound = await job.insertTemplate(structuredClone(edgyDynamicTemplate.content));
91
+ if (bound) {
92
+ const padding = 20 / gfx.viewport.zoom;
93
+ gfx.viewport.setViewportByBound(bound, [padding, padding, padding, padding], true);
94
+ }
95
+ gfx.doc.captureSync();
96
+ gfx.tool.setTool(DefaultTool);
97
+ }
98
+ /** Create a blank EDGY board (hover spotlight host) centred on the viewport. */
99
+ export function createEdgyBoard(std) {
100
+ const gfx = gfxOf(std);
101
+ if (!gfx.surface)
102
+ return;
103
+ const { centerX, centerY } = gfx.viewport;
104
+ const id = gfx.surface.addElement({
105
+ type: 'edgyBoard',
106
+ // The blank board is a frame too: the same rules apply to what is drawn on
107
+ // it, and the same map-wide arbitration has to have somewhere to live.
108
+ role: EDGY_ROLE.board,
109
+ xywh: new Bound(centerX - BOARD_W / 2, centerY - BOARD_H / 2, BOARD_W, BOARD_H).serialize(),
110
+ });
111
+ finish(gfx, id);
112
+ }
113
+ /** Create a box base element (outcome / object / activity) with inner text. */
114
+ export function createEdgyBox(std, kind) {
115
+ const gfx = gfxOf(std);
116
+ const surface = gfx.surface;
117
+ if (!surface)
118
+ return;
119
+ const { w, h } = NODE_SIZE[kind];
120
+ const { centerX: cx, centerY: cy } = gfx.viewport;
121
+ const shapeType = kind === 'activity' ? 'polygon' : 'rect';
122
+ const id = surface.addElement({
123
+ ...baseShapeProps(kind),
124
+ shapeType,
125
+ strokeWidth: NODE_STROKE_WIDTH,
126
+ radius: kind === 'outcome' ? OUTCOME_RADIUS : 0,
127
+ vertices: kind === 'activity' ? ACTIVITY_VERTICES : null,
128
+ text: NODE_LABEL[kind],
129
+ color: NODE_STROKE,
130
+ fontFamily: FontFamily.Inter,
131
+ fontSize: INNER_FONT_SIZE,
132
+ textAlign: 'center',
133
+ xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
134
+ });
135
+ finish(gfx, id);
136
+ }
137
+ /**
138
+ * Create the People base element: an (invisible) ellipse decorated with the
139
+ * person glyph by the renderer, plus a native text label below, grouped.
140
+ */
141
+ export function createEdgyPeople(std) {
142
+ const gfx = gfxOf(std);
143
+ const surface = gfx.surface;
144
+ if (!surface)
145
+ return;
146
+ const { w, h } = NODE_SIZE.people;
147
+ const { centerX: cx, centerY: cy } = gfx.viewport;
148
+ const nodeId = surface.addElement({
149
+ ...baseShapeProps('people'),
150
+ shapeType: 'ellipse',
151
+ // No visible outline — People is just the glyph; the ellipse is the bound.
152
+ strokeWidth: 0,
153
+ xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
154
+ });
155
+ const labelId = addLabel(surface, NODE_LABEL.people, cx - 60, cy + h / 2 + LABEL_GAP);
156
+ const [, result] = std.command.exec(createGroupCommand, {
157
+ elements: [nodeId, labelId],
158
+ });
159
+ finish(gfx, result.groupId || nodeId);
160
+ }
161
+ /**
162
+ * Activate the native connector tool for an EDGY RELATION: pre-styled like the
163
+ * 24 links of the metamodel template, and stamped with the GENERIC role
164
+ * `edgy:relation`.
165
+ *
166
+ * Generic on purpose, and this is the whole design (`./relation.ts`): the verb
167
+ * of an EDGY relation is determined by the ordered pair of elements it runs
168
+ * between — 24 rows, 24 distinct pairs — so there is nothing to ask the user
169
+ * and nothing to pick from a list of twenty-two. The tool arms the parent role,
170
+ * the user drags from the subject to the object, and `EdgyRelationResolver`
171
+ * writes the verb the metamodel gives that pair, as a role and as the label.
172
+ *
173
+ * The style is the template's, to the pixel: straight, `NODE_STROKE`, two units
174
+ * wide, and NO arrowhead at either end — EDGY's reference diagram draws its
175
+ * relations as bare lines and lets the verb say which way the sentence runs.
176
+ */
177
+ export function activateEdgyRelation(gfx) {
178
+ gfx.tool.setTool(ConnectorTool, {
179
+ mode: ConnectorMode.Straight,
180
+ role: EDGY_ROLE.relation,
181
+ // The look rides on the activation, never through the last-props store:
182
+ // the plain connector tool must keep the user's own style (#144 M1).
183
+ style: {
184
+ stroke: NODE_STROKE,
185
+ strokeStyle: StrokeStyle.Solid,
186
+ strokeWidth: RELATION_STROKE_WIDTH,
187
+ frontEndpointStyle: PointStyle.None,
188
+ rearEndpointStyle: PointStyle.None,
189
+ },
190
+ });
191
+ // Keep the palette open (native sub-menu behaviour): it only closes on
192
+ // re-click of the senior button, another senior tool, or Escape.
193
+ }
@@ -0,0 +1,12 @@
1
+ import { type ElementRenderer } from '@formicoidea/labre-core/blocks/surface';
2
+ import type { EdgyBoardElementModel } from '@formicoidea/labre-core/model';
3
+ /**
4
+ * Canvas renderer for the blank EDGY board: a plain white rounded rectangle
5
+ * with a discreet border. The board only exists to host free-form EDGY
6
+ * modelling and grant the spotlight-on-hover behavior to the elements laid on
7
+ * top of it.
8
+ */
9
+ export declare const edgyBoard: ElementRenderer<EdgyBoardElementModel>;
10
+ export declare const EdgyBoardRendererExtension: import("@formicoidea/labre-core/store").ExtensionType & {
11
+ identifier: import("@formicoidea/labre-core/_pkgs/global/di").ServiceIdentifier<ElementRenderer<EdgyBoardElementModel>>;
12
+ };
@@ -0,0 +1,28 @@
1
+ import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
2
+ const BOARD_FILL = '#ffffff';
3
+ const BOARD_BORDER = '#e0e0e0';
4
+ const BOARD_RADIUS = 16;
5
+ const BOARD_BORDER_WIDTH = 2;
6
+ /**
7
+ * Canvas renderer for the blank EDGY board: a plain white rounded rectangle
8
+ * with a discreet border. The board only exists to host free-form EDGY
9
+ * modelling and grant the spotlight-on-hover behavior to the elements laid on
10
+ * top of it.
11
+ */
12
+ export const edgyBoard = (model, ctx, matrix) => {
13
+ const [, , w, h] = model.deserializedXYWH;
14
+ const cx = w / 2;
15
+ const cy = h / 2;
16
+ ctx.setTransform(matrix
17
+ .translateSelf(cx, cy)
18
+ .rotateSelf(model.rotate)
19
+ .translateSelf(-cx, -cy));
20
+ ctx.beginPath();
21
+ ctx.roundRect(0, 0, w, h, BOARD_RADIUS);
22
+ ctx.fillStyle = BOARD_FILL;
23
+ ctx.fill();
24
+ ctx.strokeStyle = BOARD_BORDER;
25
+ ctx.lineWidth = BOARD_BORDER_WIDTH;
26
+ ctx.stroke();
27
+ };
28
+ export const EdgyBoardRendererExtension = ElementRendererExtension('edgyBoard', edgyBoard);
@@ -0,0 +1,11 @@
1
+ import type { EdgyBoardElementModel } from '@formicoidea/labre-core/model';
2
+ import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
3
+ export declare class EdgyBoardView extends GfxElementModelView<EdgyBoardElementModel> {
4
+ static type: string;
5
+ }
6
+ /**
7
+ * Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
8
+ * true. Moving/selecting stays available throughout (same as the facets
9
+ * diagram).
10
+ */
11
+ export declare const EdgyBoardInteraction: import("@formicoidea/labre-core/store").ExtensionType;
@@ -0,0 +1,20 @@
1
+ import { GfxElementModelView, GfxViewInteractionExtension, } from '@formicoidea/labre-core/std/gfx';
2
+ export class EdgyBoardView extends GfxElementModelView {
3
+ static { this.type = 'edgyBoard'; }
4
+ }
5
+ /**
6
+ * Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
7
+ * true. Moving/selecting stays available throughout (same as the facets
8
+ * diagram).
9
+ */
10
+ export const EdgyBoardInteraction = GfxViewInteractionExtension(EdgyBoardView.type, {
11
+ handleResize({ model }) {
12
+ return {
13
+ beforeResize({ set }) {
14
+ if (!model.resizeEnabled) {
15
+ set({ allowedHandlers: [] });
16
+ }
17
+ },
18
+ };
19
+ },
20
+ });
@@ -0,0 +1,4 @@
1
+ import type { CommandDescriptor } from '@formicoidea/labre-core/std';
2
+ import type { TemplateResult } from 'lit';
3
+ export declare const edgyCommands: CommandDescriptor[];
4
+ export declare const edgyCommandIcons: Record<string, TemplateResult>;
@@ -0,0 +1,103 @@
1
+ import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
2
+ import { activateEdgyRelation, createEdgyBoard, createEdgyBox, createEdgyDynamic, createEdgyFacets, createEdgyPeople, } from './actions.js';
3
+ import { edgyActivityIcon, edgyBoardIcon, edgyDynamicIcon, edgyFacetsIcon, edgyObjectIcon, edgyOutcomeIcon, edgyPeopleIcon, edgyRelationIcon, } from './toolbar/icons.js';
4
+ const SPECS = [
5
+ {
6
+ id: 'addFacets',
7
+ label: 'Enterprise Design facets',
8
+ iconKey: 'edgy.facets',
9
+ category: 'diagrams',
10
+ element: 'facets',
11
+ run: createEdgyFacets,
12
+ },
13
+ {
14
+ id: 'insertDynamic',
15
+ label: 'EDGY dynamic (elements & relations)',
16
+ iconKey: 'edgy.dynamic',
17
+ category: 'diagrams',
18
+ element: 'template:dynamic',
19
+ run: createEdgyDynamic,
20
+ },
21
+ {
22
+ id: 'addBoard',
23
+ label: 'EDGY board (hover spotlight)',
24
+ iconKey: 'edgy.board',
25
+ category: 'diagrams',
26
+ element: 'board',
27
+ run: createEdgyBoard,
28
+ },
29
+ {
30
+ id: 'addPeople',
31
+ label: 'People',
32
+ iconKey: 'edgy.people',
33
+ category: 'elements',
34
+ element: 'node:people',
35
+ run: createEdgyPeople,
36
+ },
37
+ {
38
+ id: 'addOutcome',
39
+ label: 'Outcome',
40
+ iconKey: 'edgy.outcome',
41
+ category: 'elements',
42
+ element: 'node:outcome',
43
+ run: std => createEdgyBox(std, 'outcome'),
44
+ },
45
+ {
46
+ id: 'addObject',
47
+ label: 'Object',
48
+ iconKey: 'edgy.object',
49
+ category: 'elements',
50
+ element: 'node:object',
51
+ run: std => createEdgyBox(std, 'object'),
52
+ },
53
+ {
54
+ id: 'addActivity',
55
+ label: 'Activity',
56
+ iconKey: 'edgy.activity',
57
+ category: 'elements',
58
+ element: 'node:activity',
59
+ run: std => createEdgyBox(std, 'activity'),
60
+ },
61
+ {
62
+ id: 'addRelation',
63
+ label: 'Relation',
64
+ description: 'Drag from the element that is the subject of the relation to the one it is about; EDGY names the link itself.',
65
+ iconKey: 'edgy.relation',
66
+ category: 'relations',
67
+ kind: 'tool',
68
+ element: 'connector:relation',
69
+ run: std => activateEdgyRelation(std.get(GfxControllerIdentifier)),
70
+ },
71
+ ];
72
+ export const edgyCommands = SPECS.map((spec, order) => ({
73
+ id: `edgy.${spec.id}`,
74
+ owner: 'edgy',
75
+ kind: spec.kind ?? 'artefact',
76
+ labelKey: `com.labre.commands.edgy.${spec.id}`,
77
+ labelFallback: spec.label,
78
+ ...(spec.description === undefined
79
+ ? {}
80
+ : {
81
+ descriptionKey: `com.labre.commands.edgy.${spec.id}.description`,
82
+ descriptionFallback: spec.description,
83
+ }),
84
+ category: spec.category,
85
+ iconKey: spec.iconKey,
86
+ surfaces: ['senior-menu', 'catalogue', 'palette', 'agent'],
87
+ order,
88
+ scope: 'edgeless',
89
+ defaultKeys: { mac: [], other: [] },
90
+ availability: 'always',
91
+ run: spec.run,
92
+ telemetry: { framework: 'edgy', element: spec.element },
93
+ }));
94
+ export const edgyCommandIcons = {
95
+ 'edgy.facets': edgyFacetsIcon,
96
+ 'edgy.dynamic': edgyDynamicIcon,
97
+ 'edgy.board': edgyBoardIcon,
98
+ 'edgy.people': edgyPeopleIcon,
99
+ 'edgy.outcome': edgyOutcomeIcon,
100
+ 'edgy.object': edgyObjectIcon,
101
+ 'edgy.activity': edgyActivityIcon,
102
+ 'edgy.relation': edgyRelationIcon,
103
+ };
package/dist/consts.d.ts CHANGED
@@ -44,3 +44,38 @@ export declare function refScale(w: number, h: number): {
44
44
  ox: number;
45
45
  oy: number;
46
46
  };
47
+ /**
48
+ * Bounding box of the three circles in reference coords (small padding). The
49
+ * REF margins around it only exist for the facet name labels — when a diagram
50
+ * hides them (`cropToCircles`), the renderer fits THIS box into the element
51
+ * bounds instead, so the background hugs the Venn.
52
+ */
53
+ export declare const CROP: {
54
+ x: number;
55
+ y: number;
56
+ w: number;
57
+ h: number;
58
+ };
59
+ /**
60
+ * Crop box KEEPING room for the three facet name labels (drawn outside the
61
+ * circles): the circles box plus a fixed text allowance left/right and below.
62
+ * Used when a cropped diagram still shows its labels.
63
+ */
64
+ export declare const CROP_LABELED: {
65
+ x: number;
66
+ y: number;
67
+ w: number;
68
+ h: number;
69
+ };
70
+ /** `refScale` variant fitting the cropped circles box (see {@link CROP}). */
71
+ export declare const cropScale: (w: number, h: number) => {
72
+ s: number;
73
+ ox: number;
74
+ oy: number;
75
+ };
76
+ /** `refScale` variant fitting the circles + facet labels box. */
77
+ export declare const cropLabeledScale: (w: number, h: number) => {
78
+ s: number;
79
+ ox: number;
80
+ oy: number;
81
+ };
package/dist/consts.js CHANGED
@@ -43,3 +43,49 @@ export function refScale(w, h) {
43
43
  const s = Math.min(w / REF_W, h / REF_H);
44
44
  return { s, ox: (w - REF_W * s) / 2, oy: (h - REF_H * s) / 2 };
45
45
  }
46
+ /**
47
+ * Bounding box of the three circles in reference coords (small padding). The
48
+ * REF margins around it only exist for the facet name labels — when a diagram
49
+ * hides them (`cropToCircles`), the renderer fits THIS box into the element
50
+ * bounds instead, so the background hugs the Venn.
51
+ */
52
+ export const CROP = (() => {
53
+ const pad = 8;
54
+ const ax = VENN.cx - 0.866 * VENN.r0; // Identity / Architecture centres
55
+ const abY = VENN.cy - 0.5 * VENN.r0;
56
+ const cY = VENN.cy + VENN.r0; // Experience centre
57
+ const minX = ax - VENN.R - pad;
58
+ const maxX = VENN.cx + 0.866 * VENN.r0 + VENN.R + pad;
59
+ const minY = abY - VENN.R - pad;
60
+ const maxY = cY + VENN.R + pad;
61
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
62
+ })();
63
+ /**
64
+ * Crop box KEEPING room for the three facet name labels (drawn outside the
65
+ * circles): the circles box plus a fixed text allowance left/right and below.
66
+ * Used when a cropped diagram still shows its labels.
67
+ */
68
+ export const CROP_LABELED = (() => {
69
+ const sideAllowance = 140; // label offset (10) + text width headroom
70
+ const bottomAllowance = 35; // experience label below the bottom circle
71
+ return {
72
+ x: CROP.x - sideAllowance,
73
+ y: CROP.y,
74
+ w: CROP.w + 2 * sideAllowance,
75
+ h: CROP.h + bottomAllowance,
76
+ };
77
+ })();
78
+ function fitScale(box) {
79
+ return (w, h) => {
80
+ const s = Math.min(w / box.w, h / box.h);
81
+ return {
82
+ s,
83
+ ox: (w - box.w * s) / 2 - box.x * s,
84
+ oy: (h - box.h * s) / 2 - box.y * s,
85
+ };
86
+ };
87
+ }
88
+ /** `refScale` variant fitting the cropped circles box (see {@link CROP}). */
89
+ export const cropScale = fitScale(CROP);
90
+ /** `refScale` variant fitting the circles + facet labels box. */
91
+ export const cropLabeledScale = fitScale(CROP_LABELED);
@@ -1,7 +1,12 @@
1
- import { EdgyViewExtension } from './view.js';
1
+ import { EdgyRenderViewExtension, EdgyViewExtension } from './view.js';
2
2
  /** Host wiring for the edgy framework. */
3
3
  export declare const edgyFramework: {
4
4
  readonly flag: "edgy";
5
- readonly telemetry: "edgy";
6
- readonly viewExtension: typeof EdgyViewExtension;
5
+ readonly telemetryKey: "edgy";
6
+ readonly extensions: readonly [{
7
+ readonly viewExtension: typeof EdgyRenderViewExtension;
8
+ }, {
9
+ readonly flag: "edgy";
10
+ readonly viewExtension: typeof EdgyViewExtension;
11
+ }];
7
12
  };
@@ -1,7 +1,10 @@
1
- import { EdgyViewExtension } from './view.js';
1
+ import { EdgyRenderViewExtension, EdgyViewExtension } from './view.js';
2
2
  /** Host wiring for the edgy framework. */
3
3
  export const edgyFramework = {
4
4
  flag: 'edgy',
5
- telemetry: 'edgy',
6
- viewExtension: EdgyViewExtension,
5
+ telemetryKey: 'edgy',
6
+ extensions: [
7
+ { viewExtension: EdgyRenderViewExtension },
8
+ { flag: 'edgy', viewExtension: EdgyViewExtension },
9
+ ],
7
10
  };
@@ -1,6 +1,6 @@
1
1
  import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
2
- import { COLORS, FONT_FAMILY, LABEL_FONT_SIZE, PICTO_STROKE, REF_H, REF_W, refScale, VENN, } from './consts.js';
3
- import { CIRCLE_A, CIRCLE_B, CIRCLE_C, facetLabelAnchors } from './label-layout.js';
2
+ import { COLORS, cropLabeledScale, cropScale, FONT_FAMILY, LABEL_FONT_SIZE, PICTO_STROKE, REF_H, REF_W, refScale, VENN, } from './consts.js';
3
+ import { CIRCLE_A, CIRCLE_B, CIRCLE_C, facetLabelAnchors, } from './label-layout.js';
4
4
  /**
5
5
  * Canvas renderer for the EDGY Enterprise Design Facets diagram — reproduces the
6
6
  * validated mockup: three overlapping circles (Identity / Architecture /
@@ -15,9 +15,18 @@ export const edgy = (model, ctx, matrix) => {
15
15
  const [, , w, h] = model.deserializedXYWH;
16
16
  const cx = w / 2;
17
17
  const cy = h / 2;
18
- ctx.setTransform(matrix.translateSelf(cx, cy).rotateSelf(model.rotate).translateSelf(-cx, -cy));
19
- // Uniform fit of the reference design, centered (letterboxed).
20
- const { s, ox, oy } = refScale(w, h);
18
+ ctx.setTransform(matrix
19
+ .translateSelf(cx, cy)
20
+ .rotateSelf(model.rotate)
21
+ .translateSelf(-cx, -cy));
22
+ // Uniform fit of the reference design, centered (letterboxed) — or, when
23
+ // the diagram is cropped, of the circles' bounding box (plus the facet
24
+ // label allowance if the labels are shown).
25
+ const { s, ox, oy } = model.cropToCircles
26
+ ? model.showLabels
27
+ ? cropLabeledScale(w, h)
28
+ : cropScale(w, h)
29
+ : refScale(w, h);
21
30
  ctx.translate(ox, oy);
22
31
  ctx.scale(s, s);
23
32
  const A = CIRCLE_A;
@@ -149,14 +158,16 @@ export const edgy = (model, ctx, matrix) => {
149
158
  ctx.lineTo(x, y + 12);
150
159
  ctx.stroke();
151
160
  };
152
- // Single-facet zone pictos.
153
- lens(A.x - 30, A.y - 22);
154
- house(B.x + 30, B.y - 22);
155
- heart(C.x, C.y + 36);
156
- // Intersection pictos at the validated sweet spots.
157
- network(VENN.cx, 114);
158
- sun(VENN.cx - 58, 206);
159
- cube(VENN.cx + 58, 206);
161
+ if (model.showPictos) {
162
+ // Single-facet zone pictos.
163
+ lens(A.x - 30, A.y - 22);
164
+ house(B.x + 30, B.y - 22);
165
+ heart(C.x, C.y + 36);
166
+ // Intersection pictos at the validated sweet spots.
167
+ network(VENN.cx, 114);
168
+ sun(VENN.cx - 58, 206);
169
+ cube(VENN.cx + 58, 206);
170
+ }
160
171
  // ── Labels (outside the circles) ────────────────────────────────────
161
172
  if (model.showLabels) {
162
173
  ctx.font = `500 ${LABEL_FONT_SIZE}px ${FONT_FAMILY}`;
@@ -1,7 +1,7 @@
1
1
  import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
3
3
  import { GfxElementModelView, GfxViewInteractionExtension, } from '@formicoidea/labre-core/std/gfx';
4
- import { refScale } from './consts.js';
4
+ import { cropLabeledScale, refScale } from './consts.js';
5
5
  import { getEdgyLabelHits, hitTestEdgyLabel, } from './label-layout.js';
6
6
  export class EdgyView extends GfxElementModelView {
7
7
  constructor() {
@@ -22,6 +22,9 @@ export class EdgyView extends GfxElementModelView {
22
22
  _onDblClick(e) {
23
23
  if (this.model.isLocked())
24
24
  return;
25
+ // No labels shown → nothing to edit (and the cropped mapping differs).
26
+ if (!this.model.showLabels)
27
+ return;
25
28
  const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
26
29
  const [bx, by, w, h] = this.model.deserializedXYWH;
27
30
  // Convert the model-space point into element-local coordinates, undoing the
@@ -35,8 +38,11 @@ export class EdgyView extends GfxElementModelView {
35
38
  lx = ux - bx;
36
39
  ly = uy - by;
37
40
  }
38
- // Map element-local coords into the fixed reference space the labels live in.
39
- const { s, ox, oy } = refScale(w, h);
41
+ // Map element-local coords into the fixed reference space the labels live
42
+ // in with the SAME fit the renderer used (cropped or letterboxed).
43
+ const { s, ox, oy } = this.model.cropToCircles
44
+ ? cropLabeledScale(w, h)
45
+ : refScale(w, h);
40
46
  const rx = (lx - ox) / s;
41
47
  const ry = (ly - oy) / s;
42
48
  const hit = hitTestEdgyLabel(getEdgyLabelHits(this.model), rx, ry);
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
- export {};
1
+ export { edgyCommandIcons, edgyCommands } from './commands.js';
2
+ export { edgyTranslationEntries } from './translations.js';
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- export {};
1
+ export { edgyCommandIcons, edgyCommands } from './commands.js';
2
+ export { edgyTranslationEntries } from './translations.js';
@@ -1,7 +1,13 @@
1
1
  import { LABEL_FONT_SIZE, VENN } from './consts.js';
2
2
  /** The three circle centres in reference coords. */
3
- export const CIRCLE_A = { x: VENN.cx - 0.866 * VENN.r0, y: VENN.cy - 0.5 * VENN.r0 }; // Identity
4
- export const CIRCLE_B = { x: VENN.cx + 0.866 * VENN.r0, y: VENN.cy - 0.5 * VENN.r0 }; // Architecture
3
+ export const CIRCLE_A = {
4
+ x: VENN.cx - 0.866 * VENN.r0,
5
+ y: VENN.cy - 0.5 * VENN.r0,
6
+ }; // Identity
7
+ export const CIRCLE_B = {
8
+ x: VENN.cx + 0.866 * VENN.r0,
9
+ y: VENN.cy - 0.5 * VENN.r0,
10
+ }; // Architecture
5
11
  export const CIRCLE_C = { x: VENN.cx, y: VENN.cy + VENN.r0 }; // Experience
6
12
  /**
7
13
  * The three facet name anchors in reference coords, positioned fully outside
@@ -0,0 +1,2 @@
1
+ import { type AutoLegendSpec } from '@formicoidea/labre-ddd-shared';
2
+ export declare const EDGY_AUTO_LEGEND: AutoLegendSpec;