@formicoidea/labre-framework-wardley 0.23.0 → 0.23.2
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.
- package/package.json +2 -2
- package/src/element-renderer.ts +242 -242
- package/src/element-view.ts +143 -143
- package/src/gradient.ts +137 -137
- package/src/label-layout.ts +126 -126
- package/src/legend.ts +438 -438
- package/src/node/consts.ts +109 -109
- package/src/node/node-renderer.ts +142 -142
- package/src/node/node-view.ts +11 -11
- package/src/toolbar/config.ts +280 -280
- package/src/toolbar/node-config.ts +28 -28
- package/src/toolbar/senior-tool.ts +11 -11
- package/src/toolbar/wardley-menu.ts +552 -552
- package/src/toolbar/wardley-senior-button.ts +154 -154
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
-
import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
|
|
3
|
-
import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
|
|
4
|
-
import { SignalWatcher } from '@formicoidea/labre-core/global/lit';
|
|
5
|
-
import { css, html, LitElement } from 'lit';
|
|
6
|
-
|
|
7
|
-
import { wardleyToolbarIcon } from './icons';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Main toolbar button (colored proposal-B icon) that opens the Wardley toolbox
|
|
11
|
-
* sub-menu above the toolbar. Styled like the other senior tools: the tile fills
|
|
12
|
-
* the 96×64 slot, is anchored to the bottom so it "rises from below", and grows
|
|
13
|
-
* slightly on hover.
|
|
14
|
-
*/
|
|
15
|
-
export class EdgelessWardleySeniorButton extends EdgelessToolbarToolMixin(
|
|
16
|
-
SignalWatcher(LitElement)
|
|
17
|
-
) {
|
|
18
|
-
static override styles = css`
|
|
19
|
-
:host,
|
|
20
|
-
.wardley-button {
|
|
21
|
-
display: block;
|
|
22
|
-
width: 100%;
|
|
23
|
-
height: 100%;
|
|
24
|
-
}
|
|
25
|
-
/* Make this 96px button the containing block of the popup's clip wrapper
|
|
26
|
-
(it is appended to our shadow root) so the sub-menu anchors to THIS
|
|
27
|
-
button — not the whole toolbar — and can be centered over it. */
|
|
28
|
-
:host {
|
|
29
|
-
position: relative;
|
|
30
|
-
}
|
|
31
|
-
.wardley-root {
|
|
32
|
-
width: 100%;
|
|
33
|
-
height: 64px;
|
|
34
|
-
position: relative;
|
|
35
|
-
overflow: hidden;
|
|
36
|
-
cursor: pointer;
|
|
37
|
-
display: flex;
|
|
38
|
-
align-items: flex-end;
|
|
39
|
-
justify-content: center;
|
|
40
|
-
}
|
|
41
|
-
.wardley-card {
|
|
42
|
-
--y: -4px;
|
|
43
|
-
--s: 1;
|
|
44
|
-
position: absolute;
|
|
45
|
-
bottom: 0;
|
|
46
|
-
width: 54px;
|
|
47
|
-
height: 54px;
|
|
48
|
-
transform: translateY(var(--y)) scale(var(--s)); /* base */
|
|
49
|
-
translate: var(--active-x, 0) var(--active-y, 0); /* actif */
|
|
50
|
-
rotate: var(--active-r, -2deg);
|
|
51
|
-
scale: var(--active-s, 1);
|
|
52
|
-
transition: transform 0.3s ease, translate 0.3s ease,
|
|
53
|
-
rotate 0.3s ease, scale 0.3s ease;
|
|
54
|
-
}
|
|
55
|
-
.wardley-card svg {
|
|
56
|
-
display: block;
|
|
57
|
-
width: 100%;
|
|
58
|
-
height: 100%;
|
|
59
|
-
}
|
|
60
|
-
.wardley-root:hover .wardley-card {
|
|
61
|
-
--y: -10px;
|
|
62
|
-
--s: 1.07;
|
|
63
|
-
}
|
|
64
|
-
`;
|
|
65
|
-
|
|
66
|
-
override enableActiveBackground = true;
|
|
67
|
-
|
|
68
|
-
// `EmptyTool` is only a sentinel for the mixin's abstract `type`; we never
|
|
69
|
-
// activate it. The menu opens as a palette over the default (selection) tool
|
|
70
|
-
// so the canvas stays fully interactive while it is open — mirroring the
|
|
71
|
-
// native Note/Shape sub-menus.
|
|
72
|
-
override type = EmptyTool;
|
|
73
|
-
|
|
74
|
-
private _toggleMenu() {
|
|
75
|
-
// Toggle on popper presence (not tool-active state): the menu stays open on
|
|
76
|
-
// click-outside and only closes on re-click, another senior tool, or Escape.
|
|
77
|
-
if (this.popper) {
|
|
78
|
-
this.popper.dispose();
|
|
79
|
-
this.popper = null;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
this.setEdgelessTool(DefaultTool);
|
|
83
|
-
const menu = this.createPopper('edgeless-wardley-menu', this);
|
|
84
|
-
menu.element.edgeless = this.edgeless;
|
|
85
|
-
|
|
86
|
-
// Anchor the sub-menu to THIS button (the clip wrapper is now button-
|
|
87
|
-
// relative thanks to `:host{position:relative}`): make the menu an in-flow
|
|
88
|
-
// flex item sized to its content, centered over the button. Now that other
|
|
89
|
-
// senior tools (EDGY, Cynefin/Estuarine) sit to the right of Wardley, there
|
|
90
|
-
// is room on both sides, so the menu no longer needs to be pinned to the
|
|
91
|
-
// right edge. Native sub-menus are untouched.
|
|
92
|
-
const el = menu.element as HTMLElement;
|
|
93
|
-
const wrap = el.parentElement;
|
|
94
|
-
if (wrap) {
|
|
95
|
-
wrap.style.overflow = 'visible';
|
|
96
|
-
wrap.style.justifyContent = 'center';
|
|
97
|
-
}
|
|
98
|
-
Object.assign(el.style, {
|
|
99
|
-
position: 'static',
|
|
100
|
-
width: 'max-content',
|
|
101
|
-
maxWidth: 'calc(100vw - 16px)',
|
|
102
|
-
marginLeft: '0',
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// The Wardley menu is wide (~13 items). After layout, right-align its right
|
|
106
|
-
// edge to the right edge of the rightmost senior tool (the right end of the
|
|
107
|
-
// senior toolbar), so it fills the space to the right instead of sitting
|
|
108
|
-
// centered with a gap. The menu then extends leftwards.
|
|
109
|
-
requestAnimationFrame(() => {
|
|
110
|
-
const rect = el.getBoundingClientRect();
|
|
111
|
-
|
|
112
|
-
// Right edge of the rightmost senior tool slot (scan across shadow roots).
|
|
113
|
-
let target = 0;
|
|
114
|
-
const seen = new Set<ShadowRoot>();
|
|
115
|
-
const scan = (root: ParentNode) => {
|
|
116
|
-
root.querySelectorAll('*').forEach(node => {
|
|
117
|
-
const cls = (node as HTMLElement).className;
|
|
118
|
-
if (
|
|
119
|
-
typeof cls === 'string' &&
|
|
120
|
-
cls.split(' ').includes('senior-tool-item')
|
|
121
|
-
) {
|
|
122
|
-
const b = node.getBoundingClientRect();
|
|
123
|
-
if (b.width > 0) target = Math.max(target, b.right);
|
|
124
|
-
}
|
|
125
|
-
const sr = (node as HTMLElement).shadowRoot;
|
|
126
|
-
if (sr && !seen.has(sr)) {
|
|
127
|
-
seen.add(sr);
|
|
128
|
-
scan(sr);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
};
|
|
132
|
-
scan(document);
|
|
133
|
-
|
|
134
|
-
if (target > 0) {
|
|
135
|
-
const dx = Math.round(target - rect.right);
|
|
136
|
-
if (dx) el.style.transform = `translateX(${dx}px)`;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
override render() {
|
|
142
|
-
return html`<edgeless-toolbar-button
|
|
143
|
-
class="wardley-button"
|
|
144
|
-
.tooltip=${this.popper ? '' : 'Wardley map'}
|
|
145
|
-
.tooltipOffset=${4}
|
|
146
|
-
.active=${!!this.popper}
|
|
147
|
-
@click=${this._toggleMenu}
|
|
148
|
-
>
|
|
149
|
-
<div class="wardley-root">
|
|
150
|
-
<div class="wardley-card">${wardleyToolbarIcon}</div>
|
|
151
|
-
</div>
|
|
152
|
-
</edgeless-toolbar-button>`;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
1
|
+
import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
|
|
3
|
+
import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
|
|
4
|
+
import { SignalWatcher } from '@formicoidea/labre-core/global/lit';
|
|
5
|
+
import { css, html, LitElement } from 'lit';
|
|
6
|
+
|
|
7
|
+
import { wardleyToolbarIcon } from './icons';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Main toolbar button (colored proposal-B icon) that opens the Wardley toolbox
|
|
11
|
+
* sub-menu above the toolbar. Styled like the other senior tools: the tile fills
|
|
12
|
+
* the 96×64 slot, is anchored to the bottom so it "rises from below", and grows
|
|
13
|
+
* slightly on hover.
|
|
14
|
+
*/
|
|
15
|
+
export class EdgelessWardleySeniorButton extends EdgelessToolbarToolMixin(
|
|
16
|
+
SignalWatcher(LitElement)
|
|
17
|
+
) {
|
|
18
|
+
static override styles = css`
|
|
19
|
+
:host,
|
|
20
|
+
.wardley-button {
|
|
21
|
+
display: block;
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
}
|
|
25
|
+
/* Make this 96px button the containing block of the popup's clip wrapper
|
|
26
|
+
(it is appended to our shadow root) so the sub-menu anchors to THIS
|
|
27
|
+
button — not the whole toolbar — and can be centered over it. */
|
|
28
|
+
:host {
|
|
29
|
+
position: relative;
|
|
30
|
+
}
|
|
31
|
+
.wardley-root {
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 64px;
|
|
34
|
+
position: relative;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: flex-end;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
}
|
|
41
|
+
.wardley-card {
|
|
42
|
+
--y: -4px;
|
|
43
|
+
--s: 1;
|
|
44
|
+
position: absolute;
|
|
45
|
+
bottom: 0;
|
|
46
|
+
width: 54px;
|
|
47
|
+
height: 54px;
|
|
48
|
+
transform: translateY(var(--y)) scale(var(--s)); /* base */
|
|
49
|
+
translate: var(--active-x, 0) var(--active-y, 0); /* actif */
|
|
50
|
+
rotate: var(--active-r, -2deg);
|
|
51
|
+
scale: var(--active-s, 1);
|
|
52
|
+
transition: transform 0.3s ease, translate 0.3s ease,
|
|
53
|
+
rotate 0.3s ease, scale 0.3s ease;
|
|
54
|
+
}
|
|
55
|
+
.wardley-card svg {
|
|
56
|
+
display: block;
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
}
|
|
60
|
+
.wardley-root:hover .wardley-card {
|
|
61
|
+
--y: -10px;
|
|
62
|
+
--s: 1.07;
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
override enableActiveBackground = true;
|
|
67
|
+
|
|
68
|
+
// `EmptyTool` is only a sentinel for the mixin's abstract `type`; we never
|
|
69
|
+
// activate it. The menu opens as a palette over the default (selection) tool
|
|
70
|
+
// so the canvas stays fully interactive while it is open — mirroring the
|
|
71
|
+
// native Note/Shape sub-menus.
|
|
72
|
+
override type = EmptyTool;
|
|
73
|
+
|
|
74
|
+
private _toggleMenu() {
|
|
75
|
+
// Toggle on popper presence (not tool-active state): the menu stays open on
|
|
76
|
+
// click-outside and only closes on re-click, another senior tool, or Escape.
|
|
77
|
+
if (this.popper) {
|
|
78
|
+
this.popper.dispose();
|
|
79
|
+
this.popper = null;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.setEdgelessTool(DefaultTool);
|
|
83
|
+
const menu = this.createPopper('edgeless-wardley-menu', this);
|
|
84
|
+
menu.element.edgeless = this.edgeless;
|
|
85
|
+
|
|
86
|
+
// Anchor the sub-menu to THIS button (the clip wrapper is now button-
|
|
87
|
+
// relative thanks to `:host{position:relative}`): make the menu an in-flow
|
|
88
|
+
// flex item sized to its content, centered over the button. Now that other
|
|
89
|
+
// senior tools (EDGY, Cynefin/Estuarine) sit to the right of Wardley, there
|
|
90
|
+
// is room on both sides, so the menu no longer needs to be pinned to the
|
|
91
|
+
// right edge. Native sub-menus are untouched.
|
|
92
|
+
const el = menu.element as HTMLElement;
|
|
93
|
+
const wrap = el.parentElement;
|
|
94
|
+
if (wrap) {
|
|
95
|
+
wrap.style.overflow = 'visible';
|
|
96
|
+
wrap.style.justifyContent = 'center';
|
|
97
|
+
}
|
|
98
|
+
Object.assign(el.style, {
|
|
99
|
+
position: 'static',
|
|
100
|
+
width: 'max-content',
|
|
101
|
+
maxWidth: 'calc(100vw - 16px)',
|
|
102
|
+
marginLeft: '0',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// The Wardley menu is wide (~13 items). After layout, right-align its right
|
|
106
|
+
// edge to the right edge of the rightmost senior tool (the right end of the
|
|
107
|
+
// senior toolbar), so it fills the space to the right instead of sitting
|
|
108
|
+
// centered with a gap. The menu then extends leftwards.
|
|
109
|
+
requestAnimationFrame(() => {
|
|
110
|
+
const rect = el.getBoundingClientRect();
|
|
111
|
+
|
|
112
|
+
// Right edge of the rightmost senior tool slot (scan across shadow roots).
|
|
113
|
+
let target = 0;
|
|
114
|
+
const seen = new Set<ShadowRoot>();
|
|
115
|
+
const scan = (root: ParentNode) => {
|
|
116
|
+
root.querySelectorAll('*').forEach(node => {
|
|
117
|
+
const cls = (node as HTMLElement).className;
|
|
118
|
+
if (
|
|
119
|
+
typeof cls === 'string' &&
|
|
120
|
+
cls.split(' ').includes('senior-tool-item')
|
|
121
|
+
) {
|
|
122
|
+
const b = node.getBoundingClientRect();
|
|
123
|
+
if (b.width > 0) target = Math.max(target, b.right);
|
|
124
|
+
}
|
|
125
|
+
const sr = (node as HTMLElement).shadowRoot;
|
|
126
|
+
if (sr && !seen.has(sr)) {
|
|
127
|
+
seen.add(sr);
|
|
128
|
+
scan(sr);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
scan(document);
|
|
133
|
+
|
|
134
|
+
if (target > 0) {
|
|
135
|
+
const dx = Math.round(target - rect.right);
|
|
136
|
+
if (dx) el.style.transform = `translateX(${dx}px)`;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
override render() {
|
|
142
|
+
return html`<edgeless-toolbar-button
|
|
143
|
+
class="wardley-button"
|
|
144
|
+
.tooltip=${this.popper ? '' : 'Wardley map'}
|
|
145
|
+
.tooltipOffset=${4}
|
|
146
|
+
.active=${!!this.popper}
|
|
147
|
+
@click=${this._toggleMenu}
|
|
148
|
+
>
|
|
149
|
+
<div class="wardley-root">
|
|
150
|
+
<div class="wardley-card">${wardleyToolbarIcon}</div>
|
|
151
|
+
</div>
|
|
152
|
+
</edgeless-toolbar-button>`;
|
|
153
|
+
}
|
|
154
|
+
}
|