Archived
fix: replace workspace hover lens with details menu
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@jmfederico/pi-web": patch
|
||||
---
|
||||
|
||||
Replace the workspace hover lens with a workspace actions/details menu so metadata remains accessible without blocking list scrolling or shifting rows.
|
||||
@@ -2,6 +2,7 @@ import { LitElement, html, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import type { Project, Workspace, WorkspaceActivity } from "../api";
|
||||
import { projectActivityIndicator } from "../workspaceActivity";
|
||||
import { actionMenuPanelStyle } from "./actionMenu";
|
||||
import { renderActivityIndicator } from "./activityBadge";
|
||||
import { activateSelectableRow, activateSelectableRowFromKeyboard } from "./selectableRow";
|
||||
import { listStyles } from "./shared";
|
||||
@@ -85,10 +86,7 @@ export class ProjectList extends LitElement {
|
||||
this.openMenuProjectId = undefined;
|
||||
return;
|
||||
}
|
||||
if (target instanceof HTMLElement) {
|
||||
const rect = target.getBoundingClientRect();
|
||||
this.menuStyle = `top: ${String(rect.bottom + 4)}px; right: ${String(window.innerWidth - rect.right)}px;`;
|
||||
}
|
||||
this.menuStyle = actionMenuPanelStyle(target);
|
||||
this.openMenuProjectId = projectId;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { customElement, property, state } from "lit/decorators.js";
|
||||
import type { SessionActivity, SessionInfo, SessionStatus } from "../api";
|
||||
import { isCachedNewSessionInfo } from "../cachedNewSessions";
|
||||
import { isSessionActive } from "../../../shared/activity";
|
||||
import { actionMenuPanelStyle } from "./actionMenu";
|
||||
import { renderActivityIndicator } from "./activityBadge";
|
||||
import { activateSelectableRow, activateSelectableRowFromKeyboard } from "./selectableRow";
|
||||
import { listStyles } from "./shared";
|
||||
@@ -131,10 +132,7 @@ export class SessionList extends LitElement {
|
||||
this.openMenuSessionId = undefined;
|
||||
return;
|
||||
}
|
||||
if (target instanceof HTMLElement) {
|
||||
const rect = target.getBoundingClientRect();
|
||||
this.menuStyle = `top: ${String(rect.bottom + 4)}px; right: ${String(window.innerWidth - rect.right)}px;`;
|
||||
}
|
||||
this.menuStyle = actionMenuPanelStyle(target);
|
||||
this.openMenuSessionId = sessionId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { LitElement, html, type PropertyValues } from "lit";
|
||||
import { LitElement, html, type PropertyValues, type TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import type { Workspace, WorkspaceActivity } from "../api";
|
||||
import type { WorkspaceLabelItem } from "../plugins/types";
|
||||
import { workspaceActivityFor, workspaceActivityIndicator } from "../workspaceActivity";
|
||||
import { actionMenuPanelStyle } from "./actionMenu";
|
||||
import { renderActivityIndicator } from "./activityBadge";
|
||||
import { focusMovedWithinCurrentTarget, rowOverflowLensStyle, shouldOpenOverflowLensFromFocus, shouldOpenOverflowLensFromPointer } from "./rowOverflowLens";
|
||||
import { activateSelectableRow, activateSelectableRowFromKeyboard } from "./selectableRow";
|
||||
import { listStyles } from "./shared";
|
||||
import { renderWorkspaceLabelItems } from "./workspaceLabel";
|
||||
import { renderWorkspaceLabelInlineItems } from "./workspaceLabel";
|
||||
|
||||
@customElement("workspace-list")
|
||||
export class WorkspaceList extends LitElement {
|
||||
@@ -19,12 +19,27 @@ export class WorkspaceList extends LitElement {
|
||||
@property({ attribute: false }) activities: Record<string, WorkspaceActivity> = {};
|
||||
@property({ attribute: false }) onSelect?: (workspace: Workspace) => void;
|
||||
@property({ attribute: false }) onToggleCollapsed?: () => void;
|
||||
@state() private overflowLensWorkspaceId: string | undefined;
|
||||
@state() private overflowLensStyle = "";
|
||||
@state() private openMenuWorkspaceId: string | undefined;
|
||||
@state() private menuStyle = "";
|
||||
|
||||
private readonly onDocumentClick = (event: MouseEvent) => {
|
||||
if (event.composedPath().includes(this)) return;
|
||||
this.openMenuWorkspaceId = undefined;
|
||||
};
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
document.addEventListener("click", this.onDocumentClick);
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
document.removeEventListener("click", this.onDocumentClick);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
protected override updated(changed: PropertyValues<this>): void {
|
||||
if (changed.has("workspaces") && this.overflowLensWorkspaceId !== undefined && !this.workspaces.some((workspace) => workspace.id === this.overflowLensWorkspaceId)) this.overflowLensWorkspaceId = undefined;
|
||||
if (changed.has("collapsed") && this.collapsed) this.overflowLensWorkspaceId = undefined;
|
||||
if (changed.has("workspaces") && this.openMenuWorkspaceId !== undefined && !this.workspaces.some((workspace) => workspace.id === this.openMenuWorkspaceId)) this.openMenuWorkspaceId = undefined;
|
||||
if (changed.has("collapsed") && this.collapsed) this.openMenuWorkspaceId = undefined;
|
||||
if ((changed.has("selected") || changed.has("workspaces") || changed.has("collapsed")) && !this.collapsed) this.scrollSelectedIntoView();
|
||||
}
|
||||
|
||||
@@ -33,28 +48,20 @@ export class WorkspaceList extends LitElement {
|
||||
<section>
|
||||
<h2>${this.renderHeading()}</h2>
|
||||
${this.collapsed ? null : this.workspaces.map((workspace) => {
|
||||
const label = `${workspace.label}${workspace.isMain ? " · main" : ""}`;
|
||||
const label = workspacePrimaryLabel(workspace);
|
||||
const items = this.workspaceLabelItems(workspace);
|
||||
return html`
|
||||
<div
|
||||
class=${`action-row workspace-row ${this.selected?.id === workspace.id ? "selected" : ""}`}
|
||||
tabindex="0"
|
||||
title=${workspace.path}
|
||||
@pointerenter=${(event: PointerEvent) => { if (shouldOpenOverflowLensFromPointer(event)) this.openOverflowLens(workspace.id, event.currentTarget); }}
|
||||
@pointerleave=${() => { this.closeOverflowLens(workspace.id); }}
|
||||
@focusin=${(event: FocusEvent) => { if (shouldOpenOverflowLensFromFocus(event)) this.openOverflowLens(workspace.id, event.currentTarget); }}
|
||||
@focusout=${(event: FocusEvent) => { this.closeOverflowLensOnFocusOut(workspace.id, event); }}
|
||||
title=${label}
|
||||
@click=${(event: MouseEvent) => { activateSelectableRow(event, () => this.onSelect?.(workspace)); }}
|
||||
@keydown=${(event: KeyboardEvent) => { this.handleWorkspaceKeydown(event, workspace); }}
|
||||
>
|
||||
<div class="action-main">
|
||||
${this.renderWorkspaceMain(label, items, workspace)}
|
||||
</div>
|
||||
${this.overflowLensWorkspaceId === workspace.id ? html`
|
||||
<div class="row-overflow-lens" style=${this.overflowLensStyle}>
|
||||
${this.renderWorkspaceMain(label, items, workspace)}
|
||||
</div>
|
||||
` : null}
|
||||
${this.renderWorkspaceMenu(label, items, workspace)}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
@@ -69,40 +76,82 @@ export class WorkspaceList extends LitElement {
|
||||
return html`<button class="section-toggle" aria-expanded=${String(!this.collapsed)} @click=${() => { this.onToggleCollapsed?.(); }}><span class="section-title"><span class="section-name">${this.collapsed ? "▸" : "▾"} Workspaces</span><small class="section-selected" title=${selectedTitle}>${selectedSummary}</small></span><small class="section-count">${this.workspaces.length}</small></button>`;
|
||||
}
|
||||
|
||||
private renderActivity(workspace: Workspace) {
|
||||
private renderActivity(workspace: Workspace): TemplateResult | undefined {
|
||||
const kind = workspaceActivityIndicator(workspaceActivityFor(workspace, this.activities));
|
||||
return renderActivityIndicator(kind, kind === "terminal" ? "Workspace terminal active" : "Workspace active") ?? "";
|
||||
return renderActivityIndicator(kind, kind === "terminal" ? "Workspace terminal active" : "Workspace active");
|
||||
}
|
||||
|
||||
private renderWorkspaceMain(label: string, items: WorkspaceLabelItem[], workspace: Workspace) {
|
||||
private renderWorkspaceMain(label: string, items: WorkspaceLabelItem[], workspace: Workspace): TemplateResult {
|
||||
return html`
|
||||
<span class="workspace-label">
|
||||
<span class="workspace-label-base">${label}</span>
|
||||
${renderWorkspaceLabelItems(items)}
|
||||
<span class="workspace-primary">
|
||||
${this.renderActivity(workspace)}
|
||||
<span class="workspace-primary-label">${label}</span>
|
||||
</span>
|
||||
<small>${this.renderActivity(workspace)}${workspace.path}</small>
|
||||
${items.length === 0 ? null : html`
|
||||
<small class="workspace-secondary">
|
||||
<span class="workspace-label">${renderWorkspaceLabelInlineItems(items)}</span>
|
||||
</small>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
private openOverflowLens(workspaceId: string, target: EventTarget | null): void {
|
||||
this.overflowLensWorkspaceId = workspaceId;
|
||||
this.overflowLensStyle = rowOverflowLensStyle(target);
|
||||
private renderWorkspaceMenu(label: string, items: WorkspaceLabelItem[], workspace: Workspace): TemplateResult {
|
||||
const open = this.openMenuWorkspaceId === workspace.id;
|
||||
const menuId = workspaceMenuId(workspace.id);
|
||||
return html`
|
||||
<div class="action-menu">
|
||||
<button
|
||||
class="action-menu-toggle"
|
||||
title="Workspace actions and details"
|
||||
aria-label=${`Actions and details for ${label}`}
|
||||
aria-expanded=${String(open)}
|
||||
aria-controls=${menuId}
|
||||
@click=${(event: MouseEvent) => { event.stopPropagation(); this.toggleMenu(workspace.id, event.currentTarget); }}
|
||||
>⋯</button>
|
||||
${open ? html`
|
||||
<div class="action-menu-panel workspace-menu-panel" id=${menuId} style=${this.menuStyle} @click=${(event: MouseEvent) => { event.stopPropagation(); }}>
|
||||
${this.renderWorkspaceDetails(label, items, workspace)}
|
||||
</div>
|
||||
` : null}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private closeOverflowLens(workspaceId: string): void {
|
||||
if (this.overflowLensWorkspaceId === workspaceId) this.overflowLensWorkspaceId = undefined;
|
||||
private renderWorkspaceDetails(label: string, items: WorkspaceLabelItem[], workspace: Workspace): TemplateResult {
|
||||
return html`
|
||||
<dl class="workspace-menu-details">
|
||||
<div class="workspace-detail-row">
|
||||
<dt>${workspace.branch === undefined ? "Workspace" : "Branch"}</dt>
|
||||
<dd>${label}</dd>
|
||||
</div>
|
||||
<div class="workspace-detail-row">
|
||||
<dt>Path</dt>
|
||||
<dd title=${workspace.path}>${workspace.path}</dd>
|
||||
</div>
|
||||
${items.length === 0 ? null : html`
|
||||
<div class="workspace-detail-row">
|
||||
<dt>Details</dt>
|
||||
<dd><span class="workspace-label">${renderWorkspaceLabelInlineItems(items)}</span></dd>
|
||||
</div>
|
||||
`}
|
||||
</dl>
|
||||
`;
|
||||
}
|
||||
|
||||
private closeOverflowLensOnFocusOut(workspaceId: string, event: FocusEvent): void {
|
||||
if (focusMovedWithinCurrentTarget(event)) return;
|
||||
this.closeOverflowLens(workspaceId);
|
||||
private toggleMenu(workspaceId: string, target: EventTarget | null): void {
|
||||
if (this.openMenuWorkspaceId === workspaceId) {
|
||||
this.openMenuWorkspaceId = undefined;
|
||||
return;
|
||||
}
|
||||
this.menuStyle = actionMenuPanelStyle(target);
|
||||
this.openMenuWorkspaceId = workspaceId;
|
||||
}
|
||||
|
||||
private handleWorkspaceKeydown(event: KeyboardEvent, workspace: Workspace): void {
|
||||
if (event.key === "Escape" && this.overflowLensWorkspaceId === workspace.id) {
|
||||
if (event.key === "Escape" && this.openMenuWorkspaceId === workspace.id) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.overflowLensWorkspaceId = undefined;
|
||||
this.openMenuWorkspaceId = undefined;
|
||||
return;
|
||||
}
|
||||
activateSelectableRowFromKeyboard(event, () => this.onSelect?.(workspace));
|
||||
@@ -114,3 +163,11 @@ export class WorkspaceList extends LitElement {
|
||||
|
||||
static override styles = listStyles;
|
||||
}
|
||||
|
||||
function workspacePrimaryLabel(workspace: Workspace): string {
|
||||
return `${workspace.branch ?? workspace.label}${workspace.isMain ? " · main" : ""}`;
|
||||
}
|
||||
|
||||
function workspaceMenuId(workspaceId: string): string {
|
||||
return `workspace-menu-${workspaceId.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
const ACTION_MENU_GAP_PX = 0;
|
||||
const ACTION_MENU_MIN_USEFUL_HEIGHT_PX = 120;
|
||||
|
||||
interface ActionMenuRect {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
}
|
||||
|
||||
export function actionMenuPanelStyle(target: EventTarget | null): string {
|
||||
if (typeof HTMLElement === "undefined" || typeof window === "undefined" || !(target instanceof HTMLElement)) return "";
|
||||
const trigger = target.getBoundingClientRect();
|
||||
const bounds = actionMenuBounds(target);
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const leftBound = Math.max(0, bounds.left);
|
||||
const rightBound = Math.min(viewportWidth, bounds.right);
|
||||
const topBound = Math.max(0, bounds.top);
|
||||
const bottomBound = Math.min(viewportHeight, bounds.bottom);
|
||||
const triggerRight = Math.min(trigger.right, rightBound);
|
||||
const availableBelow = bottomBound - trigger.bottom - ACTION_MENU_GAP_PX;
|
||||
const availableAbove = trigger.top - topBound - ACTION_MENU_GAP_PX;
|
||||
const placement = availableBelow < ACTION_MENU_MIN_USEFUL_HEIGHT_PX && availableAbove > availableBelow
|
||||
? [`bottom: ${px(viewportHeight - trigger.top + ACTION_MENU_GAP_PX)};`, `max-height: ${px(Math.max(0, availableAbove))};`]
|
||||
: [`top: ${px(trigger.bottom + ACTION_MENU_GAP_PX)};`, `max-height: ${px(Math.max(0, availableBelow))};`];
|
||||
|
||||
return [
|
||||
...placement,
|
||||
`right: ${px(Math.max(0, viewportWidth - triggerRight))};`,
|
||||
`max-width: ${px(Math.max(0, triggerRight - leftBound))};`,
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function actionMenuBounds(target: HTMLElement): ActionMenuRect {
|
||||
const root = target.getRootNode();
|
||||
if (typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot && root.host instanceof HTMLElement) return root.host.getBoundingClientRect();
|
||||
return { top: 0, right: window.innerWidth, bottom: window.innerHeight, left: 0 };
|
||||
}
|
||||
|
||||
function px(value: number): string {
|
||||
return `${String(Math.round(value))}px`;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
const LENS_MARGIN_PX = 8;
|
||||
const MAX_LENS_WIDTH_PX = 720;
|
||||
|
||||
interface RowOverflowLensRect {
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface RowOverflowLensViewport {
|
||||
width: number;
|
||||
}
|
||||
|
||||
export function rowOverflowLensStyle(target: EventTarget | null): string {
|
||||
if (typeof HTMLElement === "undefined" || !(target instanceof HTMLElement)) return "";
|
||||
const anchor = target.querySelector<HTMLElement>(".action-main") ?? target;
|
||||
return rowOverflowLensStyleForRect(anchor.getBoundingClientRect(), { width: window.innerWidth });
|
||||
}
|
||||
|
||||
export function rowOverflowLensStyleForRect(rect: RowOverflowLensRect, viewport: RowOverflowLensViewport): string {
|
||||
const left = Math.max(LENS_MARGIN_PX, rect.left);
|
||||
const availableWidth = Math.max(0, viewport.width - left - LENS_MARGIN_PX);
|
||||
const maxWidth = Math.min(MAX_LENS_WIDTH_PX, availableWidth);
|
||||
const minWidth = Math.min(rect.width, maxWidth);
|
||||
|
||||
return [
|
||||
`top: ${px(rect.top)};`,
|
||||
`left: ${px(left)};`,
|
||||
`height: ${px(rect.height)};`,
|
||||
`min-width: ${px(minWidth)};`,
|
||||
`max-width: ${px(maxWidth)};`,
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function shouldOpenOverflowLensFromPointer(event: PointerEvent): boolean {
|
||||
return event.pointerType === "mouse" || event.pointerType === "pen";
|
||||
}
|
||||
|
||||
export function shouldOpenOverflowLensFromFocus(event: FocusEvent): boolean {
|
||||
if (typeof HTMLElement === "undefined" || !(event.currentTarget instanceof HTMLElement)) return false;
|
||||
return event.currentTarget.matches(":focus-visible");
|
||||
}
|
||||
|
||||
export function focusMovedWithinCurrentTarget(event: FocusEvent): boolean {
|
||||
if (typeof HTMLElement === "undefined" || typeof Node === "undefined") return false;
|
||||
return event.currentTarget instanceof HTMLElement && event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget);
|
||||
}
|
||||
|
||||
function px(value: number): string {
|
||||
return `${String(Math.round(value))}px`;
|
||||
}
|
||||
@@ -182,7 +182,16 @@ export const listStyles = css`
|
||||
.action-main { box-sizing: border-box; min-width: 0; width: 100%; border: 1px solid var(--pi-border); border-top-right-radius: 0; border-bottom-right-radius: 0; border-top-left-radius: 8px; border-bottom-left-radius: 8px; background: var(--pi-surface); color: var(--pi-text); padding: 7px 9px 7px calc(9px + var(--depth, 0) * 16px); text-align: left; }
|
||||
.action-name { display: -webkit-box; max-height: 2.5em; overflow: hidden; overflow-wrap: anywhere; line-height: 1.25; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
|
||||
.action-row:not(.selected):hover .action-main { background: var(--pi-surface-hover); }
|
||||
.workspace-row .action-main { border-radius: 8px; }
|
||||
.workspace-row .action-main { border-radius: 8px 0 0 8px; }
|
||||
.workspace-primary { min-width: 0; display: flex; align-items: baseline; gap: 6px; }
|
||||
.workspace-primary .activity-indicator { flex: 0 0 auto; margin-right: 0; }
|
||||
.workspace-primary-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.workspace-secondary { margin-top: 3px; }
|
||||
.workspace-menu-panel { width: max-content; min-width: min(120px, calc(100vw - 16px)); padding: 8px; }
|
||||
.workspace-menu-details { display: grid; gap: 6px; margin: 0; }
|
||||
.workspace-detail-row { display: grid; grid-template-columns: minmax(58px, max-content) minmax(0, 1fr); gap: 8px; align-items: baseline; }
|
||||
.workspace-detail-row dt { color: var(--pi-muted); font-size: 12px; white-space: normal; }
|
||||
.workspace-detail-row dd { min-width: 0; margin: 0; overflow-wrap: anywhere; white-space: normal; }
|
||||
.tree-marker { color: var(--pi-dim); margin-right: 5px; }
|
||||
.badge { display: inline-block; margin-left: 5px; border: 1px solid var(--pi-border); border-radius: 999px; color: var(--pi-muted); padding: 0 5px; font-size: 11px; font-weight: 400; }
|
||||
.activity-indicator { display: inline-block; width: 7px; height: 7px; margin-right: 6px; background: var(--pi-success); animation: pulse 1s ease-in-out infinite; vertical-align: 1px; }
|
||||
@@ -191,8 +200,8 @@ export const listStyles = css`
|
||||
.action-menu { position: relative; align-self: stretch; }
|
||||
.action-menu-toggle { display: grid; place-items: center; height: 100%; min-width: 32px; padding: 0; color: var(--pi-muted); border-left: 0; border-top-left-radius: 0; border-bottom-left-radius: 0; }
|
||||
.action-menu-toggle:hover { color: var(--pi-text); background: var(--pi-surface-hover); }
|
||||
.action-menu-panel { position: fixed; z-index: 50; min-width: 120px; padding: 4px; border: 1px solid var(--pi-border); border-radius: 8px; background: var(--pi-surface); box-shadow: 0 8px 24px var(--pi-shadow); }
|
||||
.action-menu-panel button { display: block; width: 100%; text-align: left; border: 0; background: transparent; color: var(--pi-text); }
|
||||
.action-menu-panel { position: fixed; z-index: 50; box-sizing: border-box; min-width: min(120px, calc(100vw - 16px)); overflow: auto; padding: 4px; border: 1px solid var(--pi-border); border-radius: 8px; background: var(--pi-surface); box-shadow: 0 8px 24px var(--pi-shadow); overflow-wrap: anywhere; }
|
||||
.action-menu-panel button { display: block; width: 100%; text-align: left; white-space: normal; overflow-wrap: anywhere; border: 0; background: transparent; color: var(--pi-text); }
|
||||
.action-menu-panel button:hover { background: var(--pi-selection-bg); }
|
||||
button.selected { border-color: var(--pi-accent); background: var(--pi-selection-bg); }
|
||||
button:disabled { opacity: .5; cursor: not-allowed; }
|
||||
@@ -202,11 +211,8 @@ export const listStyles = css`
|
||||
.workspace-label-item, .workspace-label-render, .workspace-label-separator { color: var(--pi-muted); }
|
||||
.workspace-label-link { color: var(--pi-accent); text-decoration: none; }
|
||||
.workspace-label-link:hover, .workspace-label-link:focus { text-decoration: underline; }
|
||||
.row-overflow-lens { position: fixed; z-index: 40; box-sizing: border-box; width: max-content; overflow: hidden; border: 1px solid var(--pi-border); border-radius: 8px; background: var(--pi-surface-hover); color: var(--pi-text); padding: 7px 9px 7px calc(9px + var(--depth, 0) * 16px); cursor: pointer; }
|
||||
.action-row.selected > .row-overflow-lens { border-color: var(--pi-accent); background: var(--pi-selection-bg); }
|
||||
.row-overflow-lens small { overflow: visible; text-overflow: clip; white-space: nowrap; }
|
||||
.row-overflow-lens .workspace-label { display: inline-flex; max-width: none; overflow: visible; white-space: nowrap; flex-wrap: nowrap; }
|
||||
.row-overflow-lens .workspace-label-base, .row-overflow-lens .workspace-label-item, .row-overflow-lens .workspace-label-render { overflow: visible; text-overflow: clip; white-space: nowrap; }
|
||||
.workspace-detail-row .workspace-label { overflow: visible; white-space: normal; flex-wrap: wrap; }
|
||||
.workspace-detail-row .workspace-label-base, .workspace-detail-row .workspace-label-item, .workspace-detail-row .workspace-label-render { overflow: visible; text-overflow: clip; overflow-wrap: anywhere; white-space: normal; }
|
||||
@keyframes pulse { 0%, 100% { transform: scale(.75); opacity: .55; } 50% { transform: scale(1.2); opacity: 1; } }
|
||||
`;
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ export function renderWorkspaceLabelItems(items: WorkspaceLabelItem[] = []): Tem
|
||||
return items.map((item) => html`<span class="workspace-label-separator">·</span>${renderWorkspaceLabelItem(item)}`);
|
||||
}
|
||||
|
||||
export function renderWorkspaceLabelInlineItems(items: WorkspaceLabelItem[] = []): TemplateResult[] {
|
||||
return items.map((item, index) => html`${index === 0 ? null : html`<span class="workspace-label-separator">·</span>`}${renderWorkspaceLabelItem(item)}`);
|
||||
}
|
||||
|
||||
function renderWorkspaceLabelItem(item: WorkspaceLabelItem): TemplateResult {
|
||||
if (item.type === "render") return html`<span class="workspace-label-render">${item.render()}</span>`;
|
||||
if (item.type === "link" && isSafeHref(item.href)) {
|
||||
|
||||
Reference in New Issue
Block a user