feat(elements): add reusable context menu element for recipient role selection

This commit is contained in:
2026-05-02 19:10:06 +00:00
parent 87940efdef
commit fd53bc3db8
6 changed files with 272 additions and 11 deletions
@@ -1,5 +1,7 @@
import { DeesElement, state, html, customElement, type TemplateResult, css } from '@design.estate/dees-element';
import { actionButton, demoFields, demoRecipients, fakeDocument, icon, pill, topBar, workspaceBaseStyles, workspaceDemoFrame, type IFieldPlacement, type IRecipient, type TRecipientRole } from './sdig-workspace.shared.js';
import '../sdig-contextmenu/index.js';
import { type ISdigContextMenuAction, type ISdigContextMenuActionEventDetail } from '../sdig-contextmenu/index.js';
declare global {
interface HTMLElementTagNameMap {
@@ -109,11 +111,6 @@ export class SdigWorkspaceCompose extends DeesElement {
.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; }
.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); }
.role-hint { margin-top: -2px; margin-bottom: 10px; font-size: 10px; line-height: 1.45; color: var(--text-muted); }
.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); }
.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; }
.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; }
.context-action:hover { background: var(--hover); color: var(--text); }
.context-action[disabled] { opacity: 0.45; cursor: not-allowed; }
.page-drop-target { outline: 1px dashed transparent; outline-offset: 8px; }
.page-drop-target.drag-over { outline-color: var(--accent); }
.field-box { user-select: none; touch-action: none; }
@@ -247,6 +244,23 @@ export class SdigWorkspaceCompose extends DeesElement {
this.recipientContextMenu = null;
};
private recipientContextMenuActions(recipient: IRecipient): ISdigContextMenuAction[] {
const signerCount = this.signingRecipients().length;
return recipientRoleDefinitions.map((roleDefinition) => ({
id: roleDefinition.role,
label: roleDefinition.label,
selected: recipient.role === roleDefinition.role,
disabled: recipient.role === 'signer' && roleDefinition.role !== 'signer' && signerCount <= 1,
}));
}
private handleRecipientContextMenuAction(event: CustomEvent<ISdigContextMenuActionEventDetail>, recipient: IRecipient) {
const role = event.detail.id as TRecipientRole;
if (!recipientRoleDefinitions.some((roleDefinition) => roleDefinition.role === role)) return;
this.updateRecipientRole(recipient.id, role);
this.closeRecipientContextMenu();
}
private handleDocumentClick = (event: MouseEvent) => {
const target = event.target as HTMLElement | null;
if (target?.closest('.field-box')) return;
@@ -528,11 +542,15 @@ export class SdigWorkspaceCompose extends DeesElement {
if (!this.recipientContextMenu) return html``;
const recipient = this.recipients.find((currentRecipient) => currentRecipient.id === this.recipientContextMenu?.recipientId);
if (!recipient) return html``;
const signerCount = this.signingRecipients().length;
return html`<div class="recipient-context-menu" style="left: ${this.recipientContextMenu.x}px; top: ${this.recipientContextMenu.y}px;" @click=${(event: Event) => event.stopPropagation()}>
<div class="recipient-context-title">${recipient.name}</div>
${recipientRoleDefinitions.map((roleDefinition) => html`<button class="context-action" ?disabled=${recipient.role === 'signer' && roleDefinition.role !== 'signer' && signerCount <= 1} @click=${() => { this.updateRecipientRole(recipient.id, roleDefinition.role); this.closeRecipientContextMenu(); }}>${recipient.role === roleDefinition.role ? icon('check', 12) : html`<span style="width: 12px;"></span>`}<span>${roleDefinition.label}</span></button>`)}
</div>`;
return html`
<sdig-contextmenu
.anchorX=${this.recipientContextMenu.x}
.anchorY=${this.recipientContextMenu.y}
.title=${recipient.name}
.actions=${this.recipientContextMenuActions(recipient)}
@contextmenu-action=${(event: CustomEvent<ISdigContextMenuActionEventDetail>) => this.handleRecipientContextMenuAction(event, recipient)}
></sdig-contextmenu>
`;
}
private renderStepper(): TemplateResult {