@formicoidea/labre-framework-wardley 0.35.0 → 0.37.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.
@@ -1 +1,21 @@
1
+ import { type Color, type Palette, type ShapeElementModel } from '@formicoidea/labre-core/model';
2
+ /**
3
+ * What a picked swatch WRITES on a Wardley node.
4
+ *
5
+ * The identity on every artefact but one. A zone is drawn over the components
6
+ * it groups, so its fill is a WASH — `#c6dbfc40`, the alpha being the whole of
7
+ * what keeps the map readable underneath — and a picker that wrote the swatch
8
+ * as-is would replace it with an opaque hue and hide the map the zone annotates
9
+ * (recette of #213). So a swatch picked for an area keeps the zone's alpha.
10
+ *
11
+ * Only a bare 6-digit hex is touched: a value that already carries alpha is
12
+ * somebody's deliberate choice from the custom picker, and a theme token
13
+ * (`--affine-…`) is not a hex at all and must reach the document intact.
14
+ */
15
+ export declare function wardleyFillColor(model: ShapeElementModel, value: Color): Color;
16
+ /**
17
+ * From the default editor palette we keep ONLY the neutrals — the historical
18
+ * colours are dropped in favour of the Wardley swatches above.
19
+ */
20
+ export declare const WARDLEY_PALETTE_LIST: Palette[];
1
21
  export declare const wardleyNodeToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
@@ -1,17 +1,110 @@
1
- import { shapeToolbarConfig } from '@formicoidea/labre-core/gfx/shape';
1
+ import { neutralPalettes, paletteColorAction, shapeToolbarConfig, } from '@formicoidea/labre-core/gfx/shape';
2
+ import { WardleyNodeElementModel, } from '@formicoidea/labre-core/model';
2
3
  import { ToolbarModuleExtension, } from '@formicoidea/labre-core/shared/services';
3
4
  import { BlockFlavourIdentifier } from '@formicoidea/labre-core/std';
5
+ import { AREA_FILL, INERTIA_COLOR, METHOD_FILL, WARDLEY_RED, } from '../node/consts.js';
6
+ /** The two hex digits `AREA_FILL` carries — the zone's ~25 % opacity. */
7
+ const AREA_ALPHA = AREA_FILL.slice(-2);
8
+ /** A plain 6-digit hex, the only shape a swatch value takes. */
9
+ const SIX_DIGIT_HEX = /^#[0-9a-f]{6}$/i;
10
+ /**
11
+ * What a picked swatch WRITES on a Wardley node.
12
+ *
13
+ * The identity on every artefact but one. A zone is drawn over the components
14
+ * it groups, so its fill is a WASH — `#c6dbfc40`, the alpha being the whole of
15
+ * what keeps the map readable underneath — and a picker that wrote the swatch
16
+ * as-is would replace it with an opaque hue and hide the map the zone annotates
17
+ * (recette of #213). So a swatch picked for an area keeps the zone's alpha.
18
+ *
19
+ * Only a bare 6-digit hex is touched: a value that already carries alpha is
20
+ * somebody's deliberate choice from the custom picker, and a theme token
21
+ * (`--affine-…`) is not a hex at all and must reach the document intact.
22
+ */
23
+ export function wardleyFillColor(model, value) {
24
+ if (!(model instanceof WardleyNodeElementModel))
25
+ return value;
26
+ if (model.kind !== 'area')
27
+ return value;
28
+ if (typeof value !== 'string' || !SIX_DIGIT_HEX.test(value))
29
+ return value;
30
+ return `${value}${AREA_ALPHA}`;
31
+ }
32
+ /**
33
+ * The Wardley **evolution cycle**, surfaced as ready-made swatches in the node
34
+ * colour picker: Wonder / Peace / War, the three climatic phases a component
35
+ * travels through in Simon Wardley's pattern — saturated first, then the light
36
+ * shades for a fill under a label — followed by the three colours the notation
37
+ * itself already uses (the evolution arrow's red, the inertia bar, a method's
38
+ * neutral fill).
39
+ *
40
+ * They are SHORTCUTS, never constraints: nothing in the map reads a colour, so
41
+ * an author is free to ignore them, and the custom picker stays one click away.
42
+ */
43
+ const WARDLEY_PALETTES = [
44
+ { key: 'Wonder', value: '#3ec9f2' },
45
+ { key: 'Peace', value: '#5b9cf6' },
46
+ { key: 'War', value: '#9d6df0' },
47
+ { key: 'Wonder light', value: '#b9e9fa' },
48
+ { key: 'Peace light', value: '#c6dbfc' },
49
+ { key: 'War light', value: '#d9c9fa' },
50
+ { key: 'Wardley red', value: WARDLEY_RED },
51
+ { key: 'Inertia', value: INERTIA_COLOR },
52
+ { key: 'Method grey', value: METHOD_FILL },
53
+ ];
54
+ /**
55
+ * From the default editor palette we keep ONLY the neutrals — the historical
56
+ * colours are dropped in favour of the Wardley swatches above.
57
+ */
58
+ export const WARDLEY_PALETTE_LIST = [
59
+ ...WARDLEY_PALETTES,
60
+ ...neutralPalettes(),
61
+ ];
4
62
  /**
5
63
  * Wardley nodes are {@link ShapeElementModel} subclasses, so the shape toolbar's
6
64
  * actions (which target `getSurfaceModelsByType(ShapeElementModel)`) operate on
7
- * them directly. We re-register only the color / line-style actions under the
8
- * `wardleyNode` flavour so the circle's fill / stroke color / stroke width are
9
- * editable — while excluding the shape-only actions (switch type, add inner
10
- * text, edit vertices) that don't make sense for a Wardley node.
65
+ * them directly. We re-register only the line-style action plus a colour picker
66
+ * seeded with the Wardley swatches, so the circle's fill / stroke color / stroke
67
+ * width stay editable — while excluding the shape-only actions (switch type, add
68
+ * inner text) that don't make sense for a Wardley node.
69
+ */
70
+ const KEEP_ACTION_IDS = new Set(['d.style']);
71
+ /**
72
+ * The shape toolbar's vertex editor, kept for ONE artefact: an area drawn as a
73
+ * polygon.
74
+ *
75
+ * Every other polygon on a Wardley map has an outline that IS the notation — an
76
+ * accelerator points right and a decelerator points left, and dragging a barb
77
+ * would turn a statement about the climate into a grey blob. A zone is the
78
+ * opposite: its whole job is to follow the components it groups, so moving its
79
+ * corners is the point of choosing the polygon over the rectangle.
80
+ *
81
+ * The shape action's own `when` already asks for a single ungrouped polygon —
82
+ * which excludes the two climate arrows, since both are grouped with their name
83
+ * — but it is narrowed HERE to `kind === 'area'` rather than left to rely on
84
+ * that: an arrow selected from inside its group, or one an author ungrouped,
85
+ * would otherwise offer a gesture that can only damage it.
11
86
  */
12
- const KEEP_ACTION_IDS = new Set(['e.color', 'd.style']);
87
+ const VERTEX_ACTION_ID = 'f1.edit-vertices';
88
+ function isAreaSelection(ctx) {
89
+ const models = ctx.getSurfaceModelsByType(WardleyNodeElementModel);
90
+ return models.length > 0 && models.every(model => model.kind === 'area');
91
+ }
92
+ const areaVertexActions = shapeToolbarConfig.actions
93
+ .filter(action => action.id === VERTEX_ACTION_ID)
94
+ .map(action => {
95
+ const when = action.when;
96
+ return {
97
+ ...action,
98
+ when: (ctx) => isAreaSelection(ctx) &&
99
+ (typeof when === 'function' ? when(ctx) : when !== false),
100
+ };
101
+ });
13
102
  const wardleyNodeToolbarConfig = {
14
- actions: shapeToolbarConfig.actions.filter(action => KEEP_ACTION_IDS.has(action.id)),
103
+ actions: [
104
+ ...shapeToolbarConfig.actions.filter(action => KEEP_ACTION_IDS.has(action.id)),
105
+ ...areaVertexActions,
106
+ paletteColorAction('e.color', WARDLEY_PALETTE_LIST, wardleyFillColor),
107
+ ],
15
108
  when: shapeToolbarConfig.when,
16
109
  };
17
110
  export const wardleyNodeToolbarExtension = ToolbarModuleExtension({
package/dist/view.js CHANGED
@@ -20,6 +20,7 @@ import { WardleyElementRendererExtension } from './element-renderer.js';
20
20
  import { WardleyInteraction, WardleyView } from './element-view.js';
21
21
  import { WardleyNodeRendererExtension } from './node/node-renderer.js';
22
22
  import { WardleyNodeView } from './node/node-view.js';
23
+ import { WardleyPorterWatcher } from './node/porter-watcher.js';
23
24
  import { wardleyNodeToolbarExtension } from './toolbar/node-config.js';
24
25
  import { wardleyToolbarExtension } from './toolbar/config.js';
25
26
  import { wardleySeniorTool } from './toolbar/senior-tool.js';
@@ -51,6 +52,15 @@ export class WardleyRenderViewExtension extends ViewExtensionProvider {
51
52
  context.register(WardleyInteraction);
52
53
  context.register(wardleyToolbarExtension);
53
54
  context.register(wardleyNodeToolbarExtension);
55
+ // A Porter's-forces glyph says the same thing twice — the letter in the
56
+ // circle and the `wardley:competition` tag on it — and this keeps the two
57
+ // spellings equal in both directions. HERE, in the always-on half, for
58
+ // `docs/adr/0009`'s reason: it authors nothing. It creates no element,
59
+ // offers no button, adds no artefact; it keeps an element ALREADY in the
60
+ // document coherent with itself, which a force drawn while the Wardley
61
+ // button was on must stay when the button goes off. Edgeless-only because
62
+ // a selection, a canvas text editor and a surface are all edgeless.
63
+ context.register(WardleyPorterWatcher);
54
64
  }
55
65
  }
56
66
  }
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.35.0",
4
+ "version": "0.37.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",
@@ -31,7 +31,7 @@
31
31
  "dist"
32
32
  ],
33
33
  "dependencies": {
34
- "@formicoidea/labre-core": "0.35.0",
34
+ "@formicoidea/labre-core": "0.37.0",
35
35
  "lit": "^3.2.0"
36
36
  }
37
37
  }