hxuanwang_obsidian-cdrawer/styles.css
2026-07-19 20:11:15 +08:00

799 lines
20 KiB
CSS

/* cdrawer styles */
/* Live percentage readout next to the label-size slider in settings.
SliderComponent's built-in value display (setDisplayFormat) is gated to
Obsidian 1.13.0, above this plugin's minAppVersion, so we render it manually. */
.cd-setting-value {
min-width: 3.5em;
text-align: right;
font-variant-numeric: tabular-nums;
color: var(--text-muted);
}
.cd-diagram-svg {
display: inline-block;
background: transparent;
cursor: pointer;
/* Strokes inside the SVG use currentColor — track the theme like native math (§6.4). */
color: var(--text-normal);
}
/* Label container inside the SVG's foreignObject (render.ts). */
.cd-rendered-label {
display: inline-block;
line-height: 1.2;
color: var(--text-normal);
}
.cd-diagram-wrap {
display: block;
margin: 0.25em auto;
text-align: center;
}
.cd-diagram-wrap .cd-diagram-svg {
/* Center the SVG within the reading column instead of left-aligning it. */
margin: 0 auto;
}
.cd-error {
display: inline-block;
padding: 0.2em 0.5em;
color: var(--text-error);
background: var(--background-modifier-error);
border-radius: 4px;
font-family: var(--font-monospace);
font-size: 0.85em;
}
/* Floating grid editor overlay */
.cd-editor-overlay {
position: fixed;
z-index: 1000;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
padding: 12px;
display: flex;
flex-direction: column;
gap: 8px;
font-size: var(--font-ui-small);
color: var(--text-normal);
/* Wide enough to be a comfortable editing surface: ~90% of the viewport,
capped so it never touches the window edges. §7.3. No overflow here — the
resize handles sit outside the box (negative offsets) and would be clipped
by overflow:auto. Scrolling lives on the inner body. */
width: min(900px, 90vw);
max-width: 95vw;
max-height: 90vh;
overflow: visible;
}
/* Scrollable inner content (chrome + grid + preview). */
.cd-editor-body {
display: flex;
flex-direction: column;
gap: 8px;
overflow: auto;
min-height: 0;
}
/* Embedded mode (feature #1): de-chrome the overlay so the grid reads as part
of the page rather than a popup window. Drop the border, shadow, rounded
corners, and the viewport-relative max-size caps. Width is set in JS to match
the diagram's full-width wrapper (the reading column), so the grid aligns
with the page; height is content-driven. The grid surface itself keeps its
recessed panel so cells stay legible. */
.cd-editor-overlay.cd-editor-embedded {
background: transparent;
border: none;
border-radius: 0;
box-shadow: none;
padding: 4px;
max-width: 98vw;
max-height: none;
}
/* For a fresh insert (no diagram to track), embedded mode has no JS width set,
so fall back to the reading-column width the host would have used. */
.cd-editor-overlay.cd-editor-embedded:not(.cd-editor-inflow):not([style*="width"]) {
width: min(700px, 92vw);
}
/* In-flow embedded editor (feature #1): the root is mounted INSIDE the
diagram's wrapper as a normal block, so it occupies real layout space and the
page text reflows around it — no overlap. Override .cd-editor-overlay's
position:fixed and stretch to the wrapper's full width. */
.cd-editor-overlay.cd-editor-inflow {
position: static;
width: 100%;
z-index: auto;
}
.cd-editor-chrome {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
/* A draggable handle (the editor chrome bar and the properties-panel header).
The grabbing cursor is applied via the .is-dragging class while a drag is in
progress, since the drag continues on window-level listeners after the
pointer leaves the handle (where :active would stop applying). */
.cd-drag-handle {
cursor: grab;
}
.cd-drag-handle.is-dragging {
cursor: grabbing;
}
.cd-editor-title {
font-weight: 600;
}
.cd-editor-actions {
display: flex;
gap: 6px;
}
.cd-editor-grid {
display: grid;
gap: 4px;
position: relative;
/* A clearly recessed surface behind the cells. In dark themes the standard
background vars are too close in value to separate the two layers, so we
darken the grid further with a semi-transparent black wash and a stronger
inset border for unambiguous contrast. */
background:
linear-gradient(rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0.28)),
var(--background-secondary-alt, var(--background-secondary));
padding: 18px 6px 6px 18px;
border-radius: 4px;
box-shadow: inset 0 0 0 1px var(--background-modifier-border-hover);
}
.cd-cell {
position: relative;
min-width: 92px;
min-height: 48px;
display: flex;
align-items: center;
justify-content: center;
/* Cells sit on the grid surface as their own raised panel. */
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
cursor: text;
padding: 4px 8px;
user-select: none;
}
/* In dark themes, lift the cells a touch so they read as the top layer
against the darkened grid. The :where() keeps specificity at zero so theme
overrides still win. */
.theme-dark .cd-cell {
background: var(--background-primary);
box-shadow: 0 0 0 1px var(--background-modifier-border);
}
/* Empty cells: a more visible dashed outline so the grid scaffold reads
clearly in both light and dark themes. */
.cd-cell.is-empty {
border-style: dashed;
opacity: 0.85;
}
.cd-cell.is-focused {
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px var(--interactive-accent-hover);
}
.cd-cell-label {
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
}
.cd-cell-input {
position: absolute;
inset: 4px 8px;
border: none;
background: transparent;
color: var(--text-normal);
font: inherit;
outline: none;
text-align: center;
z-index: 2;
}
.cd-cell-preview {
pointer-events: none;
max-width: 100%;
overflow: hidden;
}
.cd-col-header,
.cd-row-header {
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7em;
opacity: 0.5;
}
.cd-add-btn {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed var(--background-modifier-border);
border-radius: 4px;
background: transparent;
color: var(--text-muted);
padding: 0 6px;
min-width: 20px;
min-height: 20px;
}
.cd-add-btn:hover {
color: var(--interactive-accent);
border-color: var(--interactive-accent);
}
.cd-remove-btn {
position: absolute;
top: -2px;
right: -2px;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--background-modifier-error);
color: var(--text-on-accent);
border: none;
font-size: 10px;
line-height: 1;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
padding: 0;
}
.cd-col-header:hover .cd-remove-btn,
.cd-row-header:hover .cd-remove-btn {
display: flex;
}
.cd-svg-layer {
position: absolute;
inset: 0;
pointer-events: none;
overflow: visible;
}
.cd-arrow-path {
fill: none;
stroke: var(--text-normal);
stroke-width: 1;
pointer-events: stroke;
cursor: pointer;
}
/* Arrow group is clickable in both the grid editor and the live preview
(both attach .cd-arrow). State here in CSS rather than per-element styles. */
.cd-arrow {
cursor: pointer;
}
/* Selected arrow (feature #2): a clear accent-color highlight on the whole
arrow — shaft, head, and any tail decoration (hook/mapsto) all carry
.cd-arrow-path, so this one rule highlights the entire arrow, not just the
shaft. Applies in both the in-grid view and the live preview (both attach
.cd-arrow + .is-selected). The thicker stroke makes the selection obvious at
a glance even among overlapping parallel arrows. */
.cd-arrow.is-selected .cd-arrow-path {
stroke: var(--interactive-accent);
stroke-width: 2;
}
.cd-arrow-hit {
fill: none;
stroke: transparent;
stroke-width: 12;
pointer-events: stroke;
cursor: pointer;
}
.cd-arrow-label {
font-size: 0.85em;
fill: var(--text-normal);
}
.cd-properties {
position: fixed;
z-index: 1002;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 6px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
font-size: var(--font-ui-smaller);
min-width: 200px;
}
/* Draggable title bar for the arrow properties panel. */
.cd-properties-header {
display: flex;
align-items: center;
justify-content: space-between;
margin: -8px -8px 2px;
padding: 5px 8px;
border-bottom: 1px solid var(--background-modifier-border);
font-weight: 600;
cursor: grab;
user-select: none;
touch-action: none;
}
.cd-properties-header:active {
cursor: grabbing;
}
/* Close button on the arrow-properties header (improvement #4). */
.cd-properties-close {
width: 18px;
height: 18px;
padding: 0;
margin-left: 4px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--text-muted);
font-size: 16px;
line-height: 1;
cursor: pointer;
}
.cd-properties-close:hover {
color: var(--text-error);
background: var(--background-modifier-error);
}
.cd-properties label {
display: flex;
flex-direction: column;
gap: 2px;
}
.cd-properties .cd-prop-row {
display: flex;
gap: 6px;
align-items: center;
}
/* Curve row: label, a range slider, and a live percent readout. */
.cd-properties .cd-curve-row {
justify-content: space-between;
}
.cd-properties .cd-curve-row input[type="range"] {
flex: 1;
min-width: 80px;
}
.cd-properties .cd-curve-value {
min-width: 4em;
text-align: right;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
.cd-properties .cd-curve-straighten {
align-self: flex-start;
padding: 2px 8px;
border-radius: 4px;
background: transparent;
color: var(--text-muted);
border: 1px solid var(--background-modifier-border);
cursor: pointer;
font-size: var(--font-ui-smaller);
}
.cd-properties .cd-curve-straighten:hover {
color: var(--interactive-accent);
border-color: var(--interactive-accent);
}
/* Draggable curve handle on a selected grid arrow: a small dot at the arc
apex. Drag it perpendicular to the shaft to set the curve. */
.cd-curve-handle {
fill: var(--interactive-accent);
stroke: var(--background-primary);
stroke-width: 1.5;
cursor: ew-resize;
pointer-events: auto;
}
.cd-curve-handle:hover {
fill: var(--interactive-accent-hover);
}
.cd-properties input[type="text"],
.cd-properties select {
width: 100%;
}
.cd-drag-line {
fill: none;
stroke: var(--interactive-accent);
stroke-width: 1;
stroke-dasharray: 3 3;
pointer-events: none;
}
/* Arrow-drag handle band around cells. Sits on top of the cell and captures
the press that begins a drag-to-draw-arrow gesture (or, on a click in place,
opens the label editor). When a cell is being edited, its input rises above
the band (z-index) so typing works normally. */
.cd-cell-drag-band {
position: absolute;
inset: 0;
pointer-events: auto;
cursor: crosshair;
}
/* --- Phase 2: floating grid editor extras --- */
.cd-editor-discard {
font-size: var(--font-ui-smaller);
padding: 2px 8px;
border-radius: 4px;
background: transparent;
color: var(--text-muted);
border: 1px solid var(--background-modifier-border);
cursor: pointer;
}
.cd-editor-discard:hover {
color: var(--text-error);
border-color: var(--background-modifier-error);
}
/* Delete-diagram button (feature #2): same weight as Discard but always
error-toned, since it removes the diagram from the note (vs. Discard, which
only abandons the edit session). */
.cd-editor-delete {
font-size: var(--font-ui-smaller);
padding: 2px 8px;
border-radius: 4px;
background: transparent;
color: var(--text-error);
border: 1px solid var(--background-modifier-error);
cursor: pointer;
}
.cd-editor-delete:hover {
background: var(--background-modifier-error);
color: var(--text-on-accent);
}
/* A selected arrow's label is draggable to reposition it (feature #3). The
foreignObject carries this class; its inner label keeps the grab cursor. */
.cd-arrow-label-drag {
cursor: grab;
}
.cd-arrow-label-drag:active {
cursor: grabbing;
}
/* Import / Export chrome buttons (§9). Same weight as Discard so the three
read as one action row. */
.cd-editor-chrome-btn {
font-size: var(--font-ui-smaller);
padding: 2px 8px;
border-radius: 4px;
background: transparent;
color: var(--text-normal);
border: 1px solid var(--background-modifier-border);
cursor: pointer;
}
.cd-editor-chrome-btn:hover {
color: var(--interactive-accent);
border-color: var(--interactive-accent);
}
/* Popover anchored under the Import / Export buttons. */
.cd-chrome-popover {
position: absolute;
z-index: 1002;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 6px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
font-size: var(--font-ui-smaller);
width: min(420px, 70vw);
}
.cd-popover-title {
font-weight: 600;
}
.cd-chrome-popover select {
align-self: flex-start;
}
.cd-popover-textarea {
width: 100%;
min-height: 120px;
resize: vertical;
font-family: var(--font-monospace);
font-size: var(--font-smaller);
background: var(--background-secondary);
color: var(--text-normal);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
padding: 6px;
}
.cd-popover-textarea:disabled {
opacity: 0.5;
}
.cd-popover-status {
color: var(--text-muted);
min-height: 1.2em;
}
.cd-popover-action {
align-self: flex-start;
padding: 3px 10px;
border-radius: 4px;
background: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
cursor: pointer;
}
.cd-popover-action:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Live draft preview, rendered with the same renderer as display mode (§7). */
.cd-editor-preview-wrap {
border-top: 1px solid var(--background-modifier-border);
padding-top: 8px;
margin-top: 4px;
overflow: auto;
max-width: 100%;
}
.cd-editor-preview {
display: flex;
justify-content: center;
}
.cd-editor-preview .cd-diagram-svg {
max-width: 100%;
height: auto;
}
/* Grid add-row / add-col buttons live in implicit grid tracks past the cells
(+row below, +col to the right). Their grid positions are set inline in JS.
Hidden by default and revealed only when the pointer is near the
corresponding edge (toggled via .cd-add-show-row / .cd-add-show-col from a
mousemove handler), so the editing surface stays uncluttered. */
.cd-add-row,
.cd-add-col {
font-size: 0.75em;
min-height: 22px;
padding: 0 8px;
align-self: center;
justify-self: center;
width: max-content;
opacity: 0;
transition: opacity 0.12s ease;
pointer-events: none;
}
.cd-editor-grid.cd-add-show-row .cd-add-row,
.cd-editor-grid.cd-add-show-col .cd-add-col {
opacity: 1;
pointer-events: auto;
}
/* Per-row / per-col remove buttons reuse .cd-remove-btn (already styled) —
give the row/col variants a fixed position on the cell's outer edge. */
.cd-row-remove {
left: -10px;
top: 50%;
transform: translateY(-50%);
right: auto;
}
.cd-col-remove {
top: -10px;
left: 50%;
transform: translateX(-50%);
}
/* --- Phase 3: Live Preview display widget (§8.2) --- */
/* The widget replaces a ```cd block in Live Preview. Match the Reading-view
wrapper's centering so a diagram reads identically in both modes. */
.cd-lp-wrap {
margin: 0.25em 0;
text-align: center;
}
.cd-lp-wrap .cd-diagram-svg {
margin: 0 auto;
}
/* Tiny placeholder shown while MathJax typesets the diagram (one frame). */
.cd-lp-placeholder {
display: inline-block;
min-height: 1em;
color: var(--text-muted);
opacity: 0.5;
letter-spacing: 2px;
}
/* --- Phase 4: hover-to-edit affordance (§8.3) --- */
/* Wrapper around a rendered diagram in either Reading view or Live Preview when
clickToEdit is OFF. The Edit button overlays the diagram and only shows on
hover/focus, so a reader scrolling the note can't open the editor by
accident. */
.cd-diagram-wrap.cd-hover-edit,
.cd-lp-wrap.cd-hover-edit {
position: relative;
}
.cd-diagram-wrap.cd-hover-edit .cd-diagram-svg,
.cd-lp-wrap.cd-hover-edit .cd-diagram-svg {
/* The SVG is non-interactive in hover mode; the button is the click target. */
cursor: default;
pointer-events: none;
}
/* clickToEdit === true: the whole SVG opens the editor on click. The base
.cd-diagram-svg already sets cursor: pointer, but state it explicitly here so
the intent is obvious and survives a future base-style change. */
.cd-diagram-wrap.cd-click-edit .cd-diagram-svg,
.cd-lp-wrap.cd-click-edit .cd-diagram-svg {
cursor: pointer;
}
.cd-edit-btn {
position: absolute;
top: 4px;
right: 4px;
display: none;
align-items: center;
gap: 4px;
padding: 2px 8px;
font-size: var(--font-ui-smaller);
color: var(--text-on-accent);
background: var(--interactive-accent);
border: none;
border-radius: 4px;
cursor: pointer;
opacity: 0.92;
z-index: 2;
}
.cd-hover-edit:hover .cd-edit-btn,
.cd-edit-btn:focus-visible {
display: inline-flex;
}
.cd-edit-btn:hover {
opacity: 1;
}
/* --- Phase 4: grid editor keyboard focus (§7.3 a11y) --- */
/* Visible focus ring on a focused (non-editing) cell, distinct from the
editing state's accent fill so keyboard users can tell which cell is
focused vs. which is being typed in. */
.cd-cell:focus-visible {
outline: 2px solid var(--interactive-accent);
outline-offset: -2px;
}
/* Cells that hold the roving tabindex anchor (tabindex=0) get a subtle ring
even without :focus-visible, so the focused cell is discoverable after a
pointer-free interaction. */
.cd-cell[tabindex="0"]:not(.is-focused) {
box-shadow: 0 0 0 1px var(--interactive-accent-hover);
}
/* --- Improvements: resize handles, disabled chrome buttons --- */
/* Resize handles on the editor overlay (improvement #2). Eight handles: four
edges + four corners, positioned around the overlay's box. They're invisible
until hovered/focused so they don't clutter the surface, but the corner handles
sit a touch larger so they're easy to grab. pointer-events:none on the handles
themselves would defeat the purpose — they stay hit-testable. */
.cd-resize-handle {
position: absolute;
z-index: 1001;
background: transparent;
pointer-events: auto;
}
.cd-resize-n { top: -4px; left: 8px; right: 8px; height: 8px; }
.cd-resize-s { bottom: -4px; left: 8px; right: 8px; height: 8px; }
.cd-resize-e { right: -4px; top: 8px; bottom: 8px; width: 8px; }
.cd-resize-w { left: -4px; top: 8px; bottom: 8px; width: 8px; }
.cd-resize-ne { top: -4px; right: -4px; width: 14px; height: 14px; }
.cd-resize-nw { top: -4px; left: -4px; width: 14px; height: 14px; }
.cd-resize-se { bottom: -4px; right: -4px; width: 14px; height: 14px; }
.cd-resize-sw { bottom: -4px; left: -4px; width: 14px; height: 14px; }
/* A faint guide line on the resizeable edges while hovered, so the affordance
is discoverable. Corner handles get a small dot. */
.cd-resize-handle:hover {
background: var(--interactive-accent);
opacity: 0.35;
}
.cd-resize-ne:hover,
.cd-resize-nw:hover,
.cd-resize-se:hover,
.cd-resize-sw:hover {
border-radius: 50%;
opacity: 0.6;
}
/* Disabled chrome buttons (Undo/Redo when their stack is empty). */
.cd-editor-chrome-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.cd-editor-chrome-btn:disabled:hover {
color: var(--text-normal);
border-color: var(--background-modifier-border);
}
/* Icon-only chrome button (the mode toggle, feature #1): square, centered
glyph, same border weight as the text buttons so the row stays uniform. */
.cd-editor-icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: auto;
min-width: 0;
padding: 2px 5px;
line-height: 0;
}
.cd-editor-icon-btn svg {
display: block;
}
/* The static diagram SVG + edit button, stashed while the embedded editor is
mounted in their place (feature #1). Collapsed with display:none (not
visibility:hidden) so they take no layout space — the in-flow editor fills the
wrapper flush, with no gap where the static diagram was. */
.cd-embedded-hidden {
display: none !important;
}