@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.
@@ -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
+ }