@formicoidea/labre-framework-wardley 0.23.0 → 0.23.1

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/consts.d.ts +72 -0
  2. package/{src/consts.ts → dist/consts.js} +63 -72
  3. package/dist/descriptor.d.ts +7 -0
  4. package/{src/descriptor.ts → dist/descriptor.js} +1 -1
  5. package/dist/effects.d.ts +10 -0
  6. package/dist/effects.js +7 -0
  7. package/dist/element-renderer.d.ts +15 -0
  8. package/dist/element-renderer.js +160 -0
  9. package/dist/element-view.d.ts +21 -0
  10. package/dist/element-view.js +122 -0
  11. package/dist/gradient.d.ts +18 -0
  12. package/dist/gradient.js +112 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +2 -0
  15. package/dist/label-layout.d.ts +21 -0
  16. package/dist/label-layout.js +73 -0
  17. package/dist/legend.d.ts +12 -0
  18. package/dist/legend.js +333 -0
  19. package/dist/node/consts.d.ts +107 -0
  20. package/{src/node/consts.ts → dist/node/consts.js} +12 -20
  21. package/dist/node/label-editor.d.ts +28 -0
  22. package/dist/node/label-editor.js +216 -0
  23. package/dist/node/node-renderer.d.ts +17 -0
  24. package/dist/node/node-renderer.js +106 -0
  25. package/{src/node/node-view.ts → dist/node/node-view.d.ts} +3 -3
  26. package/dist/node/node-view.js +10 -0
  27. package/dist/templates/index.d.ts +3 -0
  28. package/dist/templates/index.js +172 -0
  29. package/dist/templates/maps.d.ts +3 -0
  30. package/dist/templates/maps.js +247 -0
  31. package/dist/toolbar/config.d.ts +75 -0
  32. package/dist/toolbar/config.js +206 -0
  33. package/dist/toolbar/icons.d.ts +31 -0
  34. package/{src/toolbar/icons.ts → dist/toolbar/icons.js} +51 -66
  35. package/dist/toolbar/node-config.d.ts +2 -0
  36. package/{src/toolbar/node-config.ts → dist/toolbar/node-config.js} +7 -14
  37. package/dist/toolbar/senior-tool.d.ts +2 -0
  38. package/{src/toolbar/senior-tool.ts → dist/toolbar/senior-tool.js} +5 -5
  39. package/dist/toolbar/wardley-menu.d.ts +53 -0
  40. package/dist/toolbar/wardley-menu.js +408 -0
  41. package/dist/toolbar/wardley-senior-button.d.ts +18 -0
  42. package/dist/toolbar/wardley-senior-button.js +146 -0
  43. package/dist/toolbar/wardley-tool-button.d.ts +10 -0
  44. package/dist/toolbar/wardley-tool-button.js +123 -0
  45. package/dist/view.d.ts +7 -0
  46. package/dist/view.js +36 -0
  47. package/package.json +15 -6
  48. package/src/effects.ts +0 -17
  49. package/src/element-renderer.ts +0 -242
  50. package/src/element-view.ts +0 -143
  51. package/src/gradient.ts +0 -137
  52. package/src/index.ts +0 -1
  53. package/src/label-layout.ts +0 -126
  54. package/src/legend.ts +0 -438
  55. package/src/node/node-renderer.ts +0 -142
  56. package/src/templates/index.ts +0 -236
  57. package/src/templates/maps.ts +0 -283
  58. package/src/toolbar/config.ts +0 -280
  59. package/src/toolbar/wardley-menu.ts +0 -552
  60. package/src/toolbar/wardley-senior-button.ts +0 -154
  61. package/src/view.ts +0 -39
@@ -0,0 +1,123 @@
1
+ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) {
3
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
4
+ return f;
5
+ }
6
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
7
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
8
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
9
+ var _, done = false;
10
+ for (var i = decorators.length - 1; i >= 0; i--) {
11
+ var context = {};
12
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
13
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
14
+ context.addInitializer = function(f) {
15
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
16
+ extraInitializers.push(accept(f || null));
17
+ };
18
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
19
+ if (kind === "accessor") {
20
+ if (result === void 0) continue;
21
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
22
+ if (_ = accept(result.get)) descriptor.get = _;
23
+ if (_ = accept(result.set)) descriptor.set = _;
24
+ if (_ = accept(result.init)) initializers.unshift(_);
25
+ } else if (_ = accept(result)) {
26
+ if (kind === "field") initializers.unshift(_);
27
+ else descriptor[key] = _;
28
+ }
29
+ }
30
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
31
+ done = true;
32
+ };
33
+ var __runInitializers = function(thisArg, initializers, value) {
34
+ var useValue = arguments.length > 2;
35
+ for (var i = 0; i < initializers.length; i++) {
36
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
37
+ }
38
+ return useValue ? value : void 0;
39
+ };
40
+ import { Bound } from "@blocksuite/global/gfx";
41
+ import { css, html, LitElement } from "lit";
42
+ import { property } from "lit/decorators.js";
43
+ import { REF_WIDTH } from "../consts";
44
+ const WardleyIcon = html`<svg
45
+ width="24"
46
+ height="24"
47
+ viewBox="0 0 24 24"
48
+ fill="none"
49
+ stroke="currentColor"
50
+ stroke-width="1.6"
51
+ stroke-linecap="round"
52
+ stroke-linejoin="round"
53
+ >
54
+ <path d="M5 4v15h15" />
55
+ <path d="M10 9l6 5" stroke-width="1.1" />
56
+ <circle cx="10" cy="9" r="1.4" fill="currentColor" stroke="none" />
57
+ <circle cx="16" cy="14" r="1.4" fill="currentColor" stroke="none" />
58
+ </svg>`;
59
+ let EdgelessWardleyToolButton = (() => {
60
+ let _classSuper = LitElement;
61
+ let _gfx_decorators;
62
+ let _gfx_initializers = [];
63
+ let _gfx_extraInitializers = [];
64
+ return class EdgelessWardleyToolButton extends _classSuper {
65
+ static {
66
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
67
+ _gfx_decorators = [property({ attribute: false })];
68
+ __esDecorate(this, null, _gfx_decorators, { kind: "accessor", name: "gfx", static: false, private: false, access: { has: (obj) => "gfx" in obj, get: (obj) => obj.gfx, set: (obj, value) => {
69
+ obj.gfx = value;
70
+ } }, metadata: _metadata }, _gfx_initializers, _gfx_extraInitializers);
71
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
72
+ }
73
+ static {
74
+ this.styles = css`
75
+ :host {
76
+ display: flex;
77
+ }
78
+ `;
79
+ }
80
+ _addWardley() {
81
+ const { gfx } = this;
82
+ if (!gfx?.surface)
83
+ return;
84
+ let width = REF_WIDTH;
85
+ for (const el of gfx.surface.getElementsByType("wardley")) {
86
+ const [, , ew, eh] = el.deserializedXYWH;
87
+ const effWidth = Math.max(ew, eh * 16 / 9);
88
+ if (effWidth > width)
89
+ width = effWidth;
90
+ }
91
+ const height = width * 9 / 16;
92
+ const { centerX, centerY } = gfx.viewport;
93
+ const id = gfx.surface.addElement({
94
+ type: "wardley",
95
+ xywh: new Bound(centerX - width / 2, centerY - height / 2, width, height).serialize()
96
+ });
97
+ gfx.doc.captureSync();
98
+ gfx.selection.set({ elements: [id], editing: false });
99
+ }
100
+ render() {
101
+ return html`<edgeless-tool-icon-button
102
+ .tooltip=${"Wardley map"}
103
+ .iconContainerPadding=${6}
104
+ .iconSize=${"24px"}
105
+ @click=${() => this._addWardley()}
106
+ >
107
+ ${WardleyIcon}
108
+ </edgeless-tool-icon-button>`;
109
+ }
110
+ #gfx = __runInitializers(this, _gfx_initializers, void 0);
111
+ get gfx() {
112
+ return this.#gfx;
113
+ }
114
+ set gfx(_) {
115
+ this.#gfx = _;
116
+ }
117
+ constructor() {
118
+ super(...arguments);
119
+ __runInitializers(this, _gfx_extraInitializers);
120
+ }
121
+ };
122
+ })();
123
+ export { EdgelessWardleyToolButton };
package/dist/view.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { type ViewExtensionContext, ViewExtensionProvider } from '@formicoidea/labre-core/ext-loader';
2
+ export declare class WardleyViewExtension extends ViewExtensionProvider {
3
+ name: string;
4
+ effect(): void;
5
+ setup(context: ViewExtensionContext): void;
6
+ }
7
+ //# sourceMappingURL=view.d.ts.map
package/dist/view.js ADDED
@@ -0,0 +1,36 @@
1
+ import { ViewExtensionProvider, } from '@formicoidea/labre-core/ext-loader';
2
+ import { extendTemplateCategory } from '@formicoidea/labre-core/gfx/template';
3
+ import { effects } from './effects';
4
+ import { wardleyTemplateCategory } from './templates';
5
+ import { WardleyElementRendererExtension } from './element-renderer';
6
+ import { WardleyInteraction, WardleyView } from './element-view';
7
+ import { WardleyNodeRendererExtension } from './node/node-renderer';
8
+ import { WardleyNodeView } from './node/node-view';
9
+ import { wardleyNodeToolbarExtension } from './toolbar/node-config';
10
+ import { wardleyToolbarExtension } from './toolbar/config';
11
+ import { wardleySeniorTool } from './toolbar/senior-tool';
12
+ export class WardleyViewExtension extends ViewExtensionProvider {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.name = 'affine-wardley-gfx';
16
+ }
17
+ effect() {
18
+ super.effect();
19
+ effects();
20
+ extendTemplateCategory(wardleyTemplateCategory);
21
+ }
22
+ setup(context) {
23
+ super.setup(context);
24
+ context.register(WardleyView);
25
+ context.register(WardleyElementRendererExtension);
26
+ context.register(WardleyNodeView);
27
+ context.register(WardleyNodeRendererExtension);
28
+ if (this.isEdgeless(context.scope)) {
29
+ context.register(WardleyInteraction);
30
+ context.register(wardleySeniorTool);
31
+ context.register(wardleyToolbarExtension);
32
+ context.register(wardleyNodeToolbarExtension);
33
+ }
34
+ }
35
+ }
36
+ //# sourceMappingURL=view.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formicoidea/labre-framework-wardley",
3
3
  "description": "Labre wardley framework for @formicoidea/labre-core.",
4
- "version": "0.23.0",
4
+ "version": "0.23.1",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",
@@ -10,15 +10,24 @@
10
10
  ],
11
11
  "license": "MPL-2.0",
12
12
  "exports": {
13
- ".": "./src/index.ts",
14
- "./view": "./src/view.ts",
15
- "./descriptor": "./src/descriptor.ts"
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./view": {
18
+ "types": "./dist/view.d.ts",
19
+ "import": "./dist/view.js"
20
+ },
21
+ "./descriptor": {
22
+ "types": "./dist/descriptor.d.ts",
23
+ "import": "./dist/descriptor.js"
24
+ }
16
25
  },
17
26
  "files": [
18
- "src"
27
+ "dist"
19
28
  ],
20
29
  "dependencies": {
21
- "@formicoidea/labre-core": "0.23.0",
30
+ "@formicoidea/labre-core": "0.23.1",
22
31
  "lit": "^3.2.0"
23
32
  }
24
33
  }
package/src/effects.ts DELETED
@@ -1,17 +0,0 @@
1
- import { EdgelessWardleyMenu } from './toolbar/wardley-menu';
2
- import { EdgelessWardleySeniorButton } from './toolbar/wardley-senior-button';
3
-
4
- export function effects() {
5
- customElements.define('edgeless-wardley-menu', EdgelessWardleyMenu);
6
- customElements.define(
7
- 'edgeless-wardley-senior-button',
8
- EdgelessWardleySeniorButton
9
- );
10
- }
11
-
12
- declare global {
13
- interface HTMLElementTagNameMap {
14
- 'edgeless-wardley-menu': EdgelessWardleyMenu;
15
- 'edgeless-wardley-senior-button': EdgelessWardleySeniorButton;
16
- }
17
- }
@@ -1,242 +0,0 @@
1
- import {
2
- type ElementRenderer,
3
- ElementRendererExtension,
4
- } from '@formicoidea/labre-core/blocks/surface';
5
- import type { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
6
-
7
- import {
8
- ARROW,
9
- CARD_RADIUS,
10
- COLORS,
11
- EVOLUTION_BOUNDARIES,
12
- FONT_FAMILY,
13
- FONTS,
14
- LINE,
15
- MARGIN,
16
- OFFSETS,
17
- } from './consts';
18
- import { BENEFIT_ZERO_FRAC, paintGradientBackground } from './gradient';
19
-
20
- function roundRectPath(
21
- ctx: CanvasRenderingContext2D,
22
- x: number,
23
- y: number,
24
- w: number,
25
- h: number,
26
- r: number
27
- ) {
28
- const rr = Math.min(r, w / 2, h / 2);
29
- ctx.beginPath();
30
- ctx.moveTo(x + rr, y);
31
- ctx.arcTo(x + w, y, x + w, y + h, rr);
32
- ctx.arcTo(x + w, y + h, x, y + h, rr);
33
- ctx.arcTo(x, y + h, x, y, rr);
34
- ctx.arcTo(x, y, x + w, y, rr);
35
- ctx.closePath();
36
- }
37
-
38
- /**
39
- * Canvas renderer for the Wardley map background — reproduces mockup C:
40
- * an L-shaped axes frame (no top/right border), dashed evolution dividers and
41
- * the symmetric axis labels.
42
- *
43
- * All sizes (fonts, margins, offsets, strokes) are FIXED model units — they do
44
- * not scale with the element size. Only the plot interior scales.
45
- */
46
- export const wardley: ElementRenderer<WardleyBackgroundElementModel> = (
47
- model,
48
- ctx,
49
- matrix
50
- ) => {
51
- const [, , w, h] = model.deserializedXYWH;
52
- const cx = w / 2;
53
- const cy = h / 2;
54
- ctx.setTransform(
55
- matrix.translateSelf(cx, cy).rotateSelf(model.rotate).translateSelf(-cx, -cy)
56
- );
57
-
58
- const px0 = MARGIN.left;
59
- const px1 = w - MARGIN.right;
60
- const py0 = MARGIN.top;
61
- const py1 = h - MARGIN.bottom;
62
- const pw = px1 - px0;
63
- const ph = py1 - py0;
64
- const ex = (r: number) => px0 + r * pw;
65
-
66
- const line = (x1: number, y1: number, x2: number, y2: number) => {
67
- ctx.beginPath();
68
- ctx.moveTo(x1, y1);
69
- ctx.lineTo(x2, y2);
70
- ctx.stroke();
71
- };
72
- const vtext = (
73
- text: string,
74
- x: number,
75
- y: number,
76
- fontSize: number,
77
- color: string
78
- ) => {
79
- ctx.save();
80
- ctx.translate(x, y);
81
- ctx.rotate(-Math.PI / 2);
82
- ctx.font = `${fontSize}px ${FONT_FAMILY}`;
83
- ctx.fillStyle = color;
84
- ctx.textAlign = 'center';
85
- ctx.textBaseline = 'alphabetic';
86
- ctx.fillText(text, 0, 0);
87
- ctx.restore();
88
- };
89
-
90
- // ── Card (element bounds) ───────────────────────────────────────────
91
- const inset = LINE.card / 2;
92
- roundRectPath(ctx, inset, inset, w - inset * 2, h - inset * 2, CARD_RADIUS);
93
- ctx.fillStyle = COLORS.card;
94
- ctx.fill();
95
- ctx.strokeStyle = COLORS.cardBorder;
96
- ctx.lineWidth = LINE.card;
97
- ctx.stroke();
98
-
99
- // ── Curve-driven gradient variants (inscribed in the frame) ─────────
100
- // Hidden when `showGradient` is false → plain white background.
101
- if (model.variant !== 'classic' && model.showGradient) {
102
- paintGradientBackground(ctx, model.variant, px0, px1, py0, py1);
103
- if (model.variant === 'benefit') {
104
- const zy = py1 - BENEFIT_ZERO_FRAC * ph;
105
- ctx.strokeStyle = COLORS.axis;
106
- ctx.lineWidth = 1.2;
107
- ctx.beginPath();
108
- ctx.moveTo(px0, zy);
109
- ctx.lineTo(px1, zy);
110
- ctx.stroke();
111
- }
112
- }
113
-
114
- // ── Optional evolution band tints ───────────────────────────────────
115
- if (model.banded) {
116
- const starts = [0, 0.175, 0.4, 0.7];
117
- const ends = [0.175, 0.4, 0.7, 1];
118
- for (let i = 0; i < 4; i++) {
119
- ctx.fillStyle = COLORS.band[i];
120
- ctx.fillRect(ex(starts[i]), py0, ex(ends[i]) - ex(starts[i]), ph);
121
- }
122
- }
123
-
124
- // ── Evolution phase dividers (dashed) ───────────────────────────────
125
- if (model.showColumnDividers) {
126
- ctx.strokeStyle = COLORS.divider;
127
- ctx.lineWidth = LINE.divider;
128
- ctx.setLineDash([5, 5]);
129
- for (const r of EVOLUTION_BOUNDARIES) {
130
- line(ex(r), py0, ex(r), py1);
131
- }
132
- ctx.setLineDash([]);
133
- }
134
-
135
- // ── Axes (L shape) + arrowheads ─────────────────────────────────────
136
- // X and Y axes are independently toggleable. Each line stops at the base of
137
- // its arrowhead (1px overlap, hidden under the triangle) so the line never
138
- // pokes past the tip on zoom.
139
- ctx.strokeStyle = COLORS.axis;
140
- ctx.lineWidth = LINE.axis;
141
- ctx.fillStyle = COLORS.axis;
142
- if (model.showXAxis) {
143
- line(px0, py1, px1 - ARROW + 1, py1); // X axis (arrow tip at px1)
144
- ctx.beginPath(); // X arrow (points right)
145
- ctx.moveTo(px1, py1);
146
- ctx.lineTo(px1 - ARROW, py1 - ARROW / 2);
147
- ctx.lineTo(px1 - ARROW, py1 + ARROW / 2);
148
- ctx.closePath();
149
- ctx.fill();
150
- }
151
- if (model.showYAxis) {
152
- line(px0, py1, px0, py0 + ARROW - 1); // Y axis (arrow tip at py0)
153
- ctx.beginPath(); // Y arrow (points up)
154
- ctx.moveTo(px0, py0);
155
- ctx.lineTo(px0 - ARROW / 2, py0 + ARROW);
156
- ctx.lineTo(px0 + ARROW / 2, py0 + ARROW);
157
- ctx.closePath();
158
- ctx.fill();
159
- }
160
-
161
- // ── Horizontal labels ───────────────────────────────────────────────
162
- ctx.textBaseline = 'alphabetic';
163
- // Phase (column) labels (left-aligned at each zone start)
164
- if (model.showColumnLabels) {
165
- ctx.font = `${FONTS.phase}px ${FONT_FAMILY}`;
166
- ctx.fillStyle = COLORS.label;
167
- ctx.textAlign = 'left';
168
- const phases: Array<[string, number]> = [
169
- [model.phase0, 0],
170
- [model.phase1, 0.175],
171
- [model.phase2, 0.4],
172
- [model.phase3, 0.7],
173
- ];
174
- for (const [label, start] of phases) {
175
- ctx.fillText(
176
- label,
177
- ex(start) + OFFSETS.phasePad,
178
- py1 + OFFSETS.phaseBaseline
179
- );
180
- }
181
- }
182
- // "Evolution" title near the X arrow (tied to the X axis)
183
- if (model.showXAxis) {
184
- ctx.font = `${FONTS.axis}px ${FONT_FAMILY}`;
185
- ctx.fillStyle = COLORS.axis;
186
- ctx.textAlign = 'right';
187
- ctx.fillText(
188
- model.xAxisTitle,
189
- px1 - OFFSETS.evolutionPadRight,
190
- py1 + OFFSETS.phaseBaseline
191
- );
192
- }
193
- // Direction indicators (Uncharted / Industrialized, top corners)
194
- if (model.showCornerLabels) {
195
- ctx.font = `${FONTS.direction}px ${FONT_FAMILY}`;
196
- ctx.fillStyle = COLORS.label;
197
- ctx.textAlign = 'left';
198
- ctx.fillText(
199
- model.evolutionStart,
200
- px0 + OFFSETS.directionPadLeft,
201
- py0 + OFFSETS.directionTop
202
- );
203
- ctx.textAlign = 'right';
204
- ctx.fillText(
205
- model.evolutionEnd,
206
- px1 - OFFSETS.directionPadRight,
207
- py0 + OFFSETS.directionTop
208
- );
209
- }
210
-
211
- // ── Rotated Y labels (hugging the axis, symmetric with the X labels) ─
212
- if (model.showYAxis) {
213
- vtext(
214
- model.yAxisTitle,
215
- px0 - OFFSETS.yHug,
216
- (py0 + py1) / 2,
217
- FONTS.axis,
218
- COLORS.axis
219
- );
220
- }
221
- if (model.showVisibilityLabels) {
222
- vtext(
223
- model.visibilityHigh,
224
- px0 - OFFSETS.yHug,
225
- py0 + OFFSETS.visibleTop,
226
- FONTS.visibility,
227
- COLORS.label
228
- );
229
- vtext(
230
- model.visibilityLow,
231
- px0 - OFFSETS.yHug,
232
- py1 - OFFSETS.invisibleBottom,
233
- FONTS.visibility,
234
- COLORS.label
235
- );
236
- }
237
- };
238
-
239
- export const WardleyElementRendererExtension = ElementRendererExtension(
240
- 'wardley',
241
- wardley
242
- );
@@ -1,143 +0,0 @@
1
- import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
2
- import type { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
3
- import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
4
- import type { PointerEventState } from '@formicoidea/labre-core/std';
5
- import {
6
- GfxElementModelView,
7
- GfxViewInteractionExtension,
8
- } from '@formicoidea/labre-core/std/gfx';
9
-
10
- import {
11
- getWardleyLabelHits,
12
- hitTestWardleyLabel,
13
- type WardleyLabelField,
14
- } from './label-layout';
15
-
16
- export class WardleyView extends GfxElementModelView<WardleyBackgroundElementModel> {
17
- static override type: string = 'wardley';
18
-
19
- /** The in-place `<input>` used to edit a label, or null when idle. */
20
- private _labelEditor: HTMLInputElement | null = null;
21
-
22
- override onCreated(): void {
23
- super.onCreated();
24
- this.on('dblclick', e => this._onDblClick(e));
25
- }
26
-
27
- override onDestroyed(): void {
28
- this._closeLabelEditor();
29
- super.onDestroyed();
30
- }
31
-
32
- /** Double-click on a label → edit its text in place. */
33
- private _onDblClick(e: PointerEventState): void {
34
- if (this.model.isLocked()) return;
35
-
36
- const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
37
- const [bx, by, w, h] = this.model.deserializedXYWH;
38
-
39
- // Convert the model-space point into element-local coordinates, undoing the
40
- // element rotation around its center.
41
- let lx = mx - bx;
42
- let ly = my - by;
43
- const rot = this.model.rotate ?? 0;
44
- if (rot) {
45
- const center: [number, number] = [bx + w / 2, by + h / 2];
46
- const [ux, uy] = rotatePoint([mx, my], center, -rot);
47
- lx = ux - bx;
48
- ly = uy - by;
49
- }
50
-
51
- const hit = hitTestWardleyLabel(getWardleyLabelHits(this.model, w, h), lx, ly);
52
- if (!hit) return;
53
-
54
- this._openLabelEditor(hit.field, e);
55
- }
56
-
57
- private _openLabelEditor(field: WardleyLabelField, e: PointerEventState): void {
58
- this._closeLabelEditor();
59
-
60
- const input = document.createElement('input');
61
- input.value = String(this.model[field] ?? '');
62
- Object.assign(input.style, {
63
- position: 'fixed',
64
- left: `${e.raw.clientX}px`,
65
- top: `${e.raw.clientY}px`,
66
- transform: 'translate(-50%, -50%)',
67
- zIndex: '10000',
68
- minWidth: '140px',
69
- padding: '3px 8px',
70
- font: '14px Inter, sans-serif',
71
- color: 'var(--affine-text-primary-color, #1f2328)',
72
- background: 'var(--affine-background-overlay-panel-color, #ffffff)',
73
- border: '1px solid var(--affine-primary-color, #1e96eb)',
74
- borderRadius: '6px',
75
- boxShadow: 'var(--affine-shadow-2, 0 2px 8px rgba(0,0,0,0.18))',
76
- outline: 'none',
77
- });
78
- document.body.append(input);
79
- this._labelEditor = input;
80
-
81
- // Mark the element as "editing" so the global edgeless key handlers
82
- // (delete, escape, etc.) don't act on it while the user types.
83
- this.gfx.selection.set({ elements: [this.model.id], editing: true });
84
-
85
- input.focus();
86
- input.select();
87
-
88
- const commit = () => {
89
- // Guard against re-entrancy: removing the input fires `blur`, which would
90
- // otherwise call `commit` a second time.
91
- if (this._labelEditor !== input) return;
92
- const value = input.value;
93
- this._closeLabelEditor();
94
- this.gfx.std.store.captureSync();
95
- this.gfx.std
96
- .get(EdgelessCRUDIdentifier)
97
- .updateElement(this.model.id, { [field]: value });
98
- };
99
-
100
- input.addEventListener('keydown', ev => {
101
- ev.stopPropagation();
102
- if (ev.key === 'Enter') {
103
- ev.preventDefault();
104
- commit();
105
- } else if (ev.key === 'Escape') {
106
- ev.preventDefault();
107
- this._closeLabelEditor();
108
- }
109
- });
110
- input.addEventListener('blur', commit);
111
- }
112
-
113
- private _closeLabelEditor(): void {
114
- if (!this._labelEditor) return;
115
- const input = this._labelEditor;
116
- this._labelEditor = null;
117
- input.remove();
118
- if (this.isConnected) {
119
- this.gfx.selection.set({ elements: [this.model.id], editing: false });
120
- }
121
- }
122
- }
123
-
124
- /**
125
- * Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
126
- * true. `beforeResize` is re-evaluated every time the allowed handles are
127
- * computed (manager.ts), so toggling the field from the toolbar updates the
128
- * handles reactively. Moving/selecting stays available throughout.
129
- */
130
- export const WardleyInteraction = GfxViewInteractionExtension<WardleyView>(
131
- WardleyView.type,
132
- {
133
- handleResize({ model }) {
134
- return {
135
- beforeResize({ set }) {
136
- if (!model.resizeEnabled) {
137
- set({ allowedHandlers: [] });
138
- }
139
- },
140
- };
141
- },
142
- }
143
- );