@formicoidea/labre-framework-edgy 0.31.0 → 0.32.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,21 @@
1
+ import type { BlockStdScope } from '@formicoidea/labre-core/std';
2
+ export type EdgyBoxKind = 'outcome' | 'object' | 'activity';
3
+ /**
4
+ * Create the Enterprise Design Facets diagram centred on the viewport —
5
+ * cropped to the circles + facet labels (no dead margins around the Venn).
6
+ */
7
+ export declare function createEdgyFacets(std: BlockStdScope): void;
8
+ /**
9
+ * Insert the "EDGY dynamic" template (facets background + the 12 elements
10
+ * linked by the 24 verbalised relations), then bring it into view.
11
+ */
12
+ export declare function createEdgyDynamic(std: BlockStdScope): Promise<void>;
13
+ /** Create a blank EDGY board (hover spotlight host) centred on the viewport. */
14
+ export declare function createEdgyBoard(std: BlockStdScope): void;
15
+ /** Create a box base element (outcome / object / activity) with inner text. */
16
+ export declare function createEdgyBox(std: BlockStdScope, kind: EdgyBoxKind): void;
17
+ /**
18
+ * Create the People base element: an (invisible) ellipse decorated with the
19
+ * person glyph by the renderer, plus a native text label below, grouped.
20
+ */
21
+ export declare function createEdgyPeople(std: BlockStdScope): void;
@@ -0,0 +1,140 @@
1
+ import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
+ import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
3
+ import { createTemplateJob } from '@formicoidea/labre-core/gfx/template';
4
+ import { FontFamily, ShapeStyle } from '@formicoidea/labre-core/model';
5
+ import { Bound } from '@formicoidea/labre-core/global/gfx';
6
+ import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
7
+ import { CROP_LABELED } from './consts.js';
8
+ 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';
9
+ import { edgyDynamicTemplate } from './templates/index.js';
10
+ /** Default facets-diagram size (REF aspect, scaled up so it reads on canvas). */
11
+ const FACETS_SCALE = 1.5;
12
+ /** Default blank-board size. */
13
+ const BOARD_W = 1600;
14
+ const BOARD_H = 1000;
15
+ /** Height of the native People free-text label. */
16
+ const LABEL_H = LABEL_FONT_SIZE + 8;
17
+ const gfxOf = (std) => std.get(GfxControllerIdentifier);
18
+ function finish(gfx, id) {
19
+ gfx.doc.captureSync();
20
+ gfx.tool.setTool(DefaultTool);
21
+ gfx.selection.set({ elements: [id], editing: false });
22
+ // Keep the palette open (native sub-menu behaviour).
23
+ }
24
+ /** Shared props for an EDGY node shape. */
25
+ const baseShapeProps = (kind) => ({
26
+ type: 'edgyNode',
27
+ kind,
28
+ filled: true,
29
+ fillColor: NODE_FILL,
30
+ strokeColor: NODE_STROKE,
31
+ shapeStyle: ShapeStyle.General,
32
+ roughness: 0,
33
+ });
34
+ /** Add a native free-text label (Inter), used for the People node. */
35
+ function addLabel(surface, text, x, y) {
36
+ return surface.addElement({
37
+ type: 'text',
38
+ text,
39
+ fontFamily: FontFamily.Inter,
40
+ fontSize: LABEL_FONT_SIZE,
41
+ color: NODE_STROKE,
42
+ textAlign: 'center',
43
+ xywh: new Bound(x, y, 120, LABEL_H).serialize(),
44
+ });
45
+ }
46
+ /**
47
+ * Create the Enterprise Design Facets diagram centred on the viewport —
48
+ * cropped to the circles + facet labels (no dead margins around the Venn).
49
+ */
50
+ export function createEdgyFacets(std) {
51
+ const gfx = gfxOf(std);
52
+ if (!gfx.surface)
53
+ return;
54
+ const width = CROP_LABELED.w * FACETS_SCALE;
55
+ const height = CROP_LABELED.h * FACETS_SCALE;
56
+ const { centerX, centerY } = gfx.viewport;
57
+ const id = gfx.surface.addElement({
58
+ type: 'edgy',
59
+ cropToCircles: true,
60
+ xywh: new Bound(centerX - width / 2, centerY - height / 2, width, height).serialize(),
61
+ });
62
+ finish(gfx, id);
63
+ }
64
+ /**
65
+ * Insert the "EDGY dynamic" template (facets background + the 12 elements
66
+ * linked by the 24 verbalised relations), then bring it into view.
67
+ */
68
+ export async function createEdgyDynamic(std) {
69
+ const gfx = gfxOf(std);
70
+ if (!gfx.surface)
71
+ return;
72
+ const job = createTemplateJob(std, 'template');
73
+ const bound = await job.insertTemplate(structuredClone(edgyDynamicTemplate.content));
74
+ if (bound) {
75
+ const padding = 20 / gfx.viewport.zoom;
76
+ gfx.viewport.setViewportByBound(bound, [padding, padding, padding, padding], true);
77
+ }
78
+ gfx.doc.captureSync();
79
+ gfx.tool.setTool(DefaultTool);
80
+ }
81
+ /** Create a blank EDGY board (hover spotlight host) centred on the viewport. */
82
+ export function createEdgyBoard(std) {
83
+ const gfx = gfxOf(std);
84
+ if (!gfx.surface)
85
+ return;
86
+ const { centerX, centerY } = gfx.viewport;
87
+ const id = gfx.surface.addElement({
88
+ type: 'edgyBoard',
89
+ xywh: new Bound(centerX - BOARD_W / 2, centerY - BOARD_H / 2, BOARD_W, BOARD_H).serialize(),
90
+ });
91
+ finish(gfx, id);
92
+ }
93
+ /** Create a box base element (outcome / object / activity) with inner text. */
94
+ export function createEdgyBox(std, kind) {
95
+ const gfx = gfxOf(std);
96
+ const surface = gfx.surface;
97
+ if (!surface)
98
+ return;
99
+ const { w, h } = NODE_SIZE[kind];
100
+ const { centerX: cx, centerY: cy } = gfx.viewport;
101
+ const shapeType = kind === 'activity' ? 'polygon' : 'rect';
102
+ const id = surface.addElement({
103
+ ...baseShapeProps(kind),
104
+ shapeType,
105
+ strokeWidth: NODE_STROKE_WIDTH,
106
+ radius: kind === 'outcome' ? OUTCOME_RADIUS : 0,
107
+ vertices: kind === 'activity' ? ACTIVITY_VERTICES : null,
108
+ text: NODE_LABEL[kind],
109
+ color: NODE_STROKE,
110
+ fontFamily: FontFamily.Inter,
111
+ fontSize: INNER_FONT_SIZE,
112
+ textAlign: 'center',
113
+ xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
114
+ });
115
+ finish(gfx, id);
116
+ }
117
+ /**
118
+ * Create the People base element: an (invisible) ellipse decorated with the
119
+ * person glyph by the renderer, plus a native text label below, grouped.
120
+ */
121
+ export function createEdgyPeople(std) {
122
+ const gfx = gfxOf(std);
123
+ const surface = gfx.surface;
124
+ if (!surface)
125
+ return;
126
+ const { w, h } = NODE_SIZE.people;
127
+ const { centerX: cx, centerY: cy } = gfx.viewport;
128
+ const nodeId = surface.addElement({
129
+ ...baseShapeProps('people'),
130
+ shapeType: 'ellipse',
131
+ // No visible outline — People is just the glyph; the ellipse is the bound.
132
+ strokeWidth: 0,
133
+ xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
134
+ });
135
+ const labelId = addLabel(surface, NODE_LABEL.people, cx - 60, cy + h / 2 + LABEL_GAP);
136
+ const [, result] = std.command.exec(createGroupCommand, {
137
+ elements: [nodeId, labelId],
138
+ });
139
+ finish(gfx, result.groupId || nodeId);
140
+ }
@@ -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,25 @@
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.translateSelf(cx, cy).rotateSelf(model.rotate).translateSelf(-cx, -cy));
17
+ ctx.beginPath();
18
+ ctx.roundRect(0, 0, w, h, BOARD_RADIUS);
19
+ ctx.fillStyle = BOARD_FILL;
20
+ ctx.fill();
21
+ ctx.strokeStyle = BOARD_BORDER;
22
+ ctx.lineWidth = BOARD_BORDER_WIDTH;
23
+ ctx.stroke();
24
+ };
25
+ 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,85 @@
1
+ import { createEdgyBoard, createEdgyBox, createEdgyDynamic, createEdgyFacets, createEdgyPeople, } from './actions.js';
2
+ import { edgyActivityIcon, edgyBoardIcon, edgyDynamicIcon, edgyFacetsIcon, edgyObjectIcon, edgyOutcomeIcon, edgyPeopleIcon, } from './toolbar/icons.js';
3
+ const SPECS = [
4
+ {
5
+ id: 'addFacets',
6
+ label: 'Enterprise Design facets',
7
+ iconKey: 'edgy.facets',
8
+ category: 'diagrams',
9
+ element: 'facets',
10
+ run: createEdgyFacets,
11
+ },
12
+ {
13
+ id: 'insertDynamic',
14
+ label: 'EDGY dynamic (elements & relations)',
15
+ iconKey: 'edgy.dynamic',
16
+ category: 'diagrams',
17
+ element: 'template:dynamic',
18
+ run: createEdgyDynamic,
19
+ },
20
+ {
21
+ id: 'addBoard',
22
+ label: 'EDGY board (hover spotlight)',
23
+ iconKey: 'edgy.board',
24
+ category: 'diagrams',
25
+ element: 'board',
26
+ run: createEdgyBoard,
27
+ },
28
+ {
29
+ id: 'addPeople',
30
+ label: 'People',
31
+ iconKey: 'edgy.people',
32
+ category: 'elements',
33
+ element: 'node:people',
34
+ run: createEdgyPeople,
35
+ },
36
+ {
37
+ id: 'addOutcome',
38
+ label: 'Outcome',
39
+ iconKey: 'edgy.outcome',
40
+ category: 'elements',
41
+ element: 'node:outcome',
42
+ run: std => createEdgyBox(std, 'outcome'),
43
+ },
44
+ {
45
+ id: 'addObject',
46
+ label: 'Object',
47
+ iconKey: 'edgy.object',
48
+ category: 'elements',
49
+ element: 'node:object',
50
+ run: std => createEdgyBox(std, 'object'),
51
+ },
52
+ {
53
+ id: 'addActivity',
54
+ label: 'Activity',
55
+ iconKey: 'edgy.activity',
56
+ category: 'elements',
57
+ element: 'node:activity',
58
+ run: std => createEdgyBox(std, 'activity'),
59
+ },
60
+ ];
61
+ export const edgyCommands = SPECS.map((spec, order) => ({
62
+ id: `edgy.${spec.id}`,
63
+ owner: 'edgy',
64
+ kind: 'artefact',
65
+ labelKey: `com.labre.commands.edgy.${spec.id}`,
66
+ labelFallback: spec.label,
67
+ category: spec.category,
68
+ iconKey: spec.iconKey,
69
+ surfaces: ['senior-menu', 'catalogue', 'palette', 'agent'],
70
+ order,
71
+ scope: 'edgeless',
72
+ defaultKeys: { mac: [], other: [] },
73
+ availability: 'always',
74
+ run: spec.run,
75
+ telemetry: { framework: 'edgy', element: spec.element },
76
+ }));
77
+ export const edgyCommandIcons = {
78
+ 'edgy.facets': edgyFacetsIcon,
79
+ 'edgy.dynamic': edgyDynamicIcon,
80
+ 'edgy.board': edgyBoardIcon,
81
+ 'edgy.people': edgyPeopleIcon,
82
+ 'edgy.outcome': edgyOutcomeIcon,
83
+ 'edgy.object': edgyObjectIcon,
84
+ 'edgy.activity': edgyActivityIcon,
85
+ };
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,5 +1,5 @@
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';
2
+ import { COLORS, cropLabeledScale, cropScale, FONT_FAMILY, LABEL_FONT_SIZE, PICTO_STROKE, REF_H, REF_W, refScale, VENN, } from './consts.js';
3
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
@@ -16,8 +16,14 @@ export const edgy = (model, ctx, matrix) => {
16
16
  const cx = w / 2;
17
17
  const cy = h / 2;
18
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);
19
+ // Uniform fit of the reference design, centered (letterboxed) — or, when
20
+ // the diagram is cropped, of the circles' bounding box (plus the facet
21
+ // label allowance if the labels are shown).
22
+ const { s, ox, oy } = model.cropToCircles
23
+ ? model.showLabels
24
+ ? cropLabeledScale(w, h)
25
+ : cropScale(w, h)
26
+ : refScale(w, h);
21
27
  ctx.translate(ox, oy);
22
28
  ctx.scale(s, s);
23
29
  const A = CIRCLE_A;
@@ -149,14 +155,16 @@ export const edgy = (model, ctx, matrix) => {
149
155
  ctx.lineTo(x, y + 12);
150
156
  ctx.stroke();
151
157
  };
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);
158
+ if (model.showPictos) {
159
+ // Single-facet zone pictos.
160
+ lens(A.x - 30, A.y - 22);
161
+ house(B.x + 30, B.y - 22);
162
+ heart(C.x, C.y + 36);
163
+ // Intersection pictos at the validated sweet spots.
164
+ network(VENN.cx, 114);
165
+ sun(VENN.cx - 58, 206);
166
+ cube(VENN.cx + 58, 206);
167
+ }
160
168
  // ── Labels (outside the circles) ────────────────────────────────────
161
169
  if (model.showLabels) {
162
170
  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,2 +1,31 @@
1
- import { type TemplateCategory } from '@formicoidea/labre-core/gfx/template';
1
+ import { type Template, type TemplateCategory } from '@formicoidea/labre-core/gfx/template';
2
+ /**
3
+ * Background scale (from REF coords to model coords): large enough that the
4
+ * default-size nodes breathe inside each zone. The background is cropped to
5
+ * the circles (`cropToCircles`), so its xywh covers CROP × DYN_SCALE.
6
+ */
7
+ export declare const DYN_SCALE = 4.8;
8
+ /**
9
+ * The 24 canonical EDGY relations (source, target, verb, label position along
10
+ * the link) — exported for the unit tests. 7 per facet + 3 between the
11
+ * intersections. The optional 4th member mirrors the reference diagram's
12
+ * placements: verbs of intersection-outgoing links sit near the far element
13
+ * (`labelOffset.distance` ≈ .75), short peer links keep the middle.
14
+ */
15
+ export declare const EDGY_DYNAMIC_RELATIONS: [string, string, string, number?][];
16
+ export declare const EDGY_DYNAMIC_NODES: Record<string, {
17
+ kind: 'outcome' | 'object' | 'activity';
18
+ cx: number;
19
+ cy: number;
20
+ w?: number;
21
+ fill: string;
22
+ }>;
23
+ /**
24
+ * Reference coords → template model coords: the background element sits at
25
+ * (0,0) and renders the cropped circles box, so a reference point maps to
26
+ * `(p - CROP.origin) × DYN_SCALE`.
27
+ */
28
+ export declare function dynToModel(refX: number, refY: number): [number, number];
29
+ /** Exported for direct insertion from the EDGY senior toolbar menu. */
30
+ export declare const edgyDynamicTemplate: Template;
2
31
  export declare const edgyTemplateCategory: TemplateCategory;