@signature.digital/catalog 1.4.0 → 1.5.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.
- package/dist_bundle/bundle.js +413 -304
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/index.d.ts +1 -0
- package/dist_ts_web/elements/index.js +2 -1
- package/dist_ts_web/elements/sdig-contextmenu/index.d.ts +1 -0
- package/dist_ts_web/elements/sdig-contextmenu/index.js +2 -0
- package/dist_ts_web/elements/sdig-contextmenu/sdig-contextmenu.d.ts +36 -0
- package/dist_ts_web/elements/sdig-contextmenu/sdig-contextmenu.js +283 -0
- package/dist_ts_web/elements/sdig-workspace/sdig-workspace-compose.d.ts +3 -0
- package/dist_ts_web/elements/sdig-workspace/sdig-workspace-compose.js +28 -11
- package/dist_watch/bundle.js +402 -178
- package/dist_watch/bundle.js.map +4 -4
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/index.ts +1 -0
- package/ts_web/elements/sdig-contextmenu/index.ts +1 -0
- package/ts_web/elements/sdig-contextmenu/sdig-contextmenu.ts +234 -0
- package/ts_web/elements/sdig-workspace/sdig-workspace-compose.ts +28 -10
package/package.json
CHANGED
package/ts_web/elements/index.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sdig-contextmenu.js';
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { DeesElement, property, state, html, customElement, type TemplateResult, css } from '@design.estate/dees-element';
|
|
2
|
+
|
|
3
|
+
export interface ISdigContextMenuAction {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
selected?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
danger?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ISdigContextMenuActionEventDetail {
|
|
13
|
+
id: string;
|
|
14
|
+
action: ISdigContextMenuAction;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type TMenuPosition = {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
ready: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
declare global {
|
|
24
|
+
interface HTMLElementTagNameMap {
|
|
25
|
+
'sdig-contextmenu': SdigContextmenu;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@customElement('sdig-contextmenu')
|
|
30
|
+
export class SdigContextmenu extends DeesElement {
|
|
31
|
+
public static demo = () => html`
|
|
32
|
+
<div style="position: relative; min-height: 260px; padding: 24px; --bg-card: hsl(0 0% 7%); --bg-input: hsl(0 0% 9%); --border: hsl(0 0% 14.9%); --border-subtle: hsl(0 0% 11%); --text: hsl(0 0% 98%); --text-sec: hsl(0 0% 63.9%); --text-muted: hsl(0 0% 48%); --hover: rgba(255,255,255,0.06); --error: #ef4444;">
|
|
33
|
+
<sdig-contextmenu
|
|
34
|
+
.anchorX=${80}
|
|
35
|
+
.anchorY=${70}
|
|
36
|
+
.title=${'Recipient'}
|
|
37
|
+
.actions=${[
|
|
38
|
+
{ id: 'signer', label: 'Needs signature', selected: true },
|
|
39
|
+
{ id: 'copy', label: 'Final copy only' },
|
|
40
|
+
{ id: 'updates', label: 'Every step update' },
|
|
41
|
+
]}
|
|
42
|
+
></sdig-contextmenu>
|
|
43
|
+
</div>
|
|
44
|
+
`;
|
|
45
|
+
public static demoGroups = ['Signature Digital Primitives'];
|
|
46
|
+
|
|
47
|
+
@property({ type: Number }) public accessor anchorX: number = 0;
|
|
48
|
+
@property({ type: Number }) public accessor anchorY: number = 0;
|
|
49
|
+
@property({ type: String }) public accessor title: string = '';
|
|
50
|
+
@property({ attribute: false }) public accessor actions: ISdigContextMenuAction[] = [];
|
|
51
|
+
@state() private accessor position: TMenuPosition = { x: 0, y: 0, ready: false };
|
|
52
|
+
|
|
53
|
+
private positionUpdateFrame: number | null = null;
|
|
54
|
+
|
|
55
|
+
public static styles = css`
|
|
56
|
+
:host { display: contents; }
|
|
57
|
+
|
|
58
|
+
.menu {
|
|
59
|
+
position: fixed;
|
|
60
|
+
z-index: 1000;
|
|
61
|
+
min-width: 190px;
|
|
62
|
+
max-width: min(280px, calc(100vw - 16px));
|
|
63
|
+
padding: 6px;
|
|
64
|
+
border: 1px solid var(--border, hsl(0 0% 14.9%));
|
|
65
|
+
border-radius: 8px;
|
|
66
|
+
background: var(--bg-card, hsl(0 0% 7%));
|
|
67
|
+
color: var(--text, hsl(0 0% 98%));
|
|
68
|
+
box-shadow: 0 16px 42px rgba(0,0,0,0.36);
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.title {
|
|
73
|
+
padding: 7px 8px;
|
|
74
|
+
margin-bottom: 4px;
|
|
75
|
+
border-bottom: 1px solid var(--border-subtle, hsl(0 0% 11%));
|
|
76
|
+
font-size: 11px;
|
|
77
|
+
font-weight: 700;
|
|
78
|
+
color: var(--text-sec, hsl(0 0% 63.9%));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.action {
|
|
82
|
+
width: 100%;
|
|
83
|
+
min-height: 34px;
|
|
84
|
+
padding: 8px;
|
|
85
|
+
border: 0;
|
|
86
|
+
border-radius: 6px;
|
|
87
|
+
background: transparent;
|
|
88
|
+
color: var(--text-sec, hsl(0 0% 63.9%));
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
text-align: left;
|
|
93
|
+
font: inherit;
|
|
94
|
+
font-size: 11px;
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.action:hover { background: var(--hover, rgba(255,255,255,0.06)); color: var(--text, hsl(0 0% 98%)); }
|
|
99
|
+
.action.danger { color: var(--error, #ef4444); }
|
|
100
|
+
.action[disabled] { opacity: 0.45; cursor: not-allowed; }
|
|
101
|
+
.action[disabled]:hover { background: transparent; color: var(--text-sec, hsl(0 0% 63.9%)); }
|
|
102
|
+
|
|
103
|
+
.action-mark {
|
|
104
|
+
width: 12px;
|
|
105
|
+
height: 12px;
|
|
106
|
+
display: inline-flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
flex-shrink: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.action-mark.selected::before {
|
|
113
|
+
content: '';
|
|
114
|
+
width: 7px;
|
|
115
|
+
height: 4px;
|
|
116
|
+
border-left: 1.5px solid currentColor;
|
|
117
|
+
border-bottom: 1.5px solid currentColor;
|
|
118
|
+
transform: rotate(-45deg) translate(1px, -1px);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.action-copy {
|
|
122
|
+
min-width: 0;
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
gap: 2px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.action-label {
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
white-space: nowrap;
|
|
131
|
+
text-overflow: ellipsis;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.action-description {
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
white-space: nowrap;
|
|
137
|
+
text-overflow: ellipsis;
|
|
138
|
+
color: var(--text-muted, hsl(0 0% 48%));
|
|
139
|
+
font-size: 10px;
|
|
140
|
+
line-height: 1.25;
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
public connectedCallback = async () => {
|
|
145
|
+
await super.connectedCallback();
|
|
146
|
+
window.addEventListener('resize', this.queuePositionUpdate);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
public disconnectedCallback = async () => {
|
|
150
|
+
window.removeEventListener('resize', this.queuePositionUpdate);
|
|
151
|
+
if (this.positionUpdateFrame !== null) {
|
|
152
|
+
globalThis.cancelAnimationFrame(this.positionUpdateFrame);
|
|
153
|
+
this.positionUpdateFrame = null;
|
|
154
|
+
}
|
|
155
|
+
await super.disconnectedCallback();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
public updated() {
|
|
159
|
+
this.queuePositionUpdate();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private queuePositionUpdate = () => {
|
|
163
|
+
if (this.positionUpdateFrame !== null) return;
|
|
164
|
+
this.positionUpdateFrame = globalThis.requestAnimationFrame(() => {
|
|
165
|
+
this.positionUpdateFrame = null;
|
|
166
|
+
this.positionMenu();
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
private positionMenu() {
|
|
171
|
+
const menu = this.shadowRoot?.querySelector('.menu') as HTMLElement | null;
|
|
172
|
+
if (!menu) return;
|
|
173
|
+
|
|
174
|
+
const margin = 8;
|
|
175
|
+
const gap = 4;
|
|
176
|
+
const rect = menu.getBoundingClientRect();
|
|
177
|
+
const viewportWidth = globalThis.innerWidth;
|
|
178
|
+
const viewportHeight = globalThis.innerHeight;
|
|
179
|
+
const spaceRight = viewportWidth - this.anchorX - margin;
|
|
180
|
+
const spaceLeft = this.anchorX - margin;
|
|
181
|
+
const spaceBelow = viewportHeight - this.anchorY - margin;
|
|
182
|
+
const spaceAbove = this.anchorY - margin;
|
|
183
|
+
let x = this.anchorX + gap;
|
|
184
|
+
let y = this.anchorY + gap;
|
|
185
|
+
|
|
186
|
+
if (spaceRight < rect.width + gap && spaceLeft > spaceRight) {
|
|
187
|
+
x = this.anchorX - rect.width - gap;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (spaceBelow < rect.height + gap && spaceAbove > spaceBelow) {
|
|
191
|
+
y = this.anchorY - rect.height - gap;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const maxX = Math.max(margin, viewportWidth - rect.width - margin);
|
|
195
|
+
const maxY = Math.max(margin, viewportHeight - rect.height - margin);
|
|
196
|
+
const nextPosition = {
|
|
197
|
+
x: Math.round(Math.max(margin, Math.min(maxX, x))),
|
|
198
|
+
y: Math.round(Math.max(margin, Math.min(maxY, y))),
|
|
199
|
+
ready: true,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (this.position.x !== nextPosition.x || this.position.y !== nextPosition.y || this.position.ready !== nextPosition.ready) {
|
|
203
|
+
this.position = nextPosition;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private selectAction(action: ISdigContextMenuAction) {
|
|
208
|
+
if (action.disabled) return;
|
|
209
|
+
this.dispatchEvent(new CustomEvent<ISdigContextMenuActionEventDetail>('contextmenu-action', {
|
|
210
|
+
detail: { id: action.id, action },
|
|
211
|
+
bubbles: true,
|
|
212
|
+
composed: true,
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public render(): TemplateResult {
|
|
217
|
+
const x = this.position.ready ? this.position.x : this.anchorX;
|
|
218
|
+
const y = this.position.ready ? this.position.y : this.anchorY;
|
|
219
|
+
return html`
|
|
220
|
+
<div class="menu" style="left: ${x}px; top: ${y}px; visibility: ${this.position.ready ? 'visible' : 'hidden'};" @click=${(event: Event) => event.stopPropagation()} @contextmenu=${(event: Event) => event.preventDefault()}>
|
|
221
|
+
${this.title ? html`<div class="title">${this.title}</div>` : ''}
|
|
222
|
+
${this.actions.map((action) => html`
|
|
223
|
+
<button class="action ${action.danger ? 'danger' : ''}" ?disabled=${action.disabled} @click=${() => this.selectAction(action)}>
|
|
224
|
+
<span class="action-mark ${action.selected ? 'selected' : ''}"></span>
|
|
225
|
+
<span class="action-copy">
|
|
226
|
+
<span class="action-label">${action.label}</span>
|
|
227
|
+
${action.description ? html`<span class="action-description">${action.description}</span>` : ''}
|
|
228
|
+
</span>
|
|
229
|
+
</button>
|
|
230
|
+
`)}
|
|
231
|
+
</div>
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { DeesElement, state, html, customElement, type TemplateResult, css } from '@design.estate/dees-element';
|
|
2
2
|
import { actionButton, demoFields, demoRecipients, fakeDocument, icon, pill, topBar, workspaceBaseStyles, workspaceDemoFrame, type IFieldPlacement, type IRecipient, type TRecipientRole } from './sdig-workspace.shared.js';
|
|
3
|
+
import '../sdig-contextmenu/index.js';
|
|
4
|
+
import { type ISdigContextMenuAction, type ISdigContextMenuActionEventDetail } from '../sdig-contextmenu/index.js';
|
|
3
5
|
|
|
4
6
|
declare global {
|
|
5
7
|
interface HTMLElementTagNameMap {
|
|
@@ -109,11 +111,6 @@ export class SdigWorkspaceCompose extends DeesElement {
|
|
|
109
111
|
.signing-placeholder { position: absolute; left: 0; right: 0; top: var(--routing-top); height: var(--routing-row-height); border: 1.5px dashed var(--accent); border-radius: 6px; background: transparent; pointer-events: none; transition: top 0.16s ease; }
|
|
110
112
|
.signing-drag-overlay { position: absolute; left: 0; right: 0; z-index: 6; top: var(--routing-top); margin-bottom: 0; cursor: grabbing; pointer-events: none; border-color: var(--accent); box-shadow: 0 10px 28px rgba(0,0,0,0.28); transform: scale(1.015); }
|
|
111
113
|
.role-hint { margin-top: -2px; margin-bottom: 10px; font-size: 10px; line-height: 1.45; color: var(--text-muted); }
|
|
112
|
-
.recipient-context-menu { position: fixed; z-index: 100; min-width: 190px; padding: 6px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-card); box-shadow: 0 16px 42px rgba(0,0,0,0.36); }
|
|
113
|
-
.recipient-context-title { padding: 7px 8px; font-size: 11px; font-weight: 700; color: var(--text-sec); border-bottom: 1px solid var(--border-subtle); margin-bottom: 4px; }
|
|
114
|
-
.context-action { width: 100%; padding: 8px; border-radius: 6px; background: transparent; color: var(--text-sec); display: flex; align-items: center; gap: 8px; text-align: left; font-size: 11px; }
|
|
115
|
-
.context-action:hover { background: var(--hover); color: var(--text); }
|
|
116
|
-
.context-action[disabled] { opacity: 0.45; cursor: not-allowed; }
|
|
117
114
|
.page-drop-target { outline: 1px dashed transparent; outline-offset: 8px; }
|
|
118
115
|
.page-drop-target.drag-over { outline-color: var(--accent); }
|
|
119
116
|
.field-box { user-select: none; touch-action: none; }
|
|
@@ -247,6 +244,23 @@ export class SdigWorkspaceCompose extends DeesElement {
|
|
|
247
244
|
this.recipientContextMenu = null;
|
|
248
245
|
};
|
|
249
246
|
|
|
247
|
+
private recipientContextMenuActions(recipient: IRecipient): ISdigContextMenuAction[] {
|
|
248
|
+
const signerCount = this.signingRecipients().length;
|
|
249
|
+
return recipientRoleDefinitions.map((roleDefinition) => ({
|
|
250
|
+
id: roleDefinition.role,
|
|
251
|
+
label: roleDefinition.label,
|
|
252
|
+
selected: recipient.role === roleDefinition.role,
|
|
253
|
+
disabled: recipient.role === 'signer' && roleDefinition.role !== 'signer' && signerCount <= 1,
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private handleRecipientContextMenuAction(event: CustomEvent<ISdigContextMenuActionEventDetail>, recipient: IRecipient) {
|
|
258
|
+
const role = event.detail.id as TRecipientRole;
|
|
259
|
+
if (!recipientRoleDefinitions.some((roleDefinition) => roleDefinition.role === role)) return;
|
|
260
|
+
this.updateRecipientRole(recipient.id, role);
|
|
261
|
+
this.closeRecipientContextMenu();
|
|
262
|
+
}
|
|
263
|
+
|
|
250
264
|
private handleDocumentClick = (event: MouseEvent) => {
|
|
251
265
|
const target = event.target as HTMLElement | null;
|
|
252
266
|
if (target?.closest('.field-box')) return;
|
|
@@ -528,11 +542,15 @@ export class SdigWorkspaceCompose extends DeesElement {
|
|
|
528
542
|
if (!this.recipientContextMenu) return html``;
|
|
529
543
|
const recipient = this.recipients.find((currentRecipient) => currentRecipient.id === this.recipientContextMenu?.recipientId);
|
|
530
544
|
if (!recipient) return html``;
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
545
|
+
return html`
|
|
546
|
+
<sdig-contextmenu
|
|
547
|
+
.anchorX=${this.recipientContextMenu.x}
|
|
548
|
+
.anchorY=${this.recipientContextMenu.y}
|
|
549
|
+
.title=${recipient.name}
|
|
550
|
+
.actions=${this.recipientContextMenuActions(recipient)}
|
|
551
|
+
@contextmenu-action=${(event: CustomEvent<ISdigContextMenuActionEventDetail>) => this.handleRecipientContextMenuAction(event, recipient)}
|
|
552
|
+
></sdig-contextmenu>
|
|
553
|
+
`;
|
|
536
554
|
}
|
|
537
555
|
|
|
538
556
|
private renderStepper(): TemplateResult {
|