05/26 Update form editor

This commit is contained in:
2026-05-26 13:13:28 -04:00
parent 5d6019ea7f
commit a2eb08807f
3 changed files with 53 additions and 21 deletions
+2 -2
View File
@@ -32,7 +32,7 @@
display: grid;
grid-template-columns: repeat(12, 72px);
grid-auto-rows: 52px; /* matches editor CELL_H — keeps proportions tight */
gap: 4px 8px;
gap: 8px;
width: max-content;
}
@@ -42,7 +42,7 @@
Falls back to a safe 100% - padding calculation if JS hasn't run yet. */
@media (max-width: 1194px) {
.form-grid {
--fg-gap: 4px;
--fg-gap: 8px;
--fg-w: calc(100% - 2rem); /* JS overrides this with measured px value */
--fg-cell: calc((var(--fg-w) - 11 * var(--fg-gap)) / 12);
grid-template-columns: repeat(12, var(--fg-cell));
+39 -18
View File
@@ -174,6 +174,7 @@
border-bottom: 1px solid var(--border);
cursor: move; flex-shrink: 0;
user-select: none;
touch-action: none; /* allow pointerdown drag without triggering scroll */
}
.fcard-badge {
font-size: .6rem; font-weight: 700;
@@ -229,11 +230,12 @@
/* resize handle — bottom-right */
.resize-h {
position: absolute; bottom: 0; right: 0;
width: 18px; height: 18px;
width: 24px; height: 24px; /* larger touch target on iPad */
cursor: se-resize; z-index: 30;
display: flex; align-items: center; justify-content: center;
color: #cbd5e1; font-size: .65rem;
transition: color var(--tr);
touch-action: none; /* allow pointerdown drag without triggering scroll */
}
.fcard.sel .resize-h { color: var(--accent); }
@@ -651,8 +653,8 @@ function buildCard(f) {
<div class="resize-h" title="Drag to resize"><i class="bi bi-arrows-angle-expand"></i></div>
`;
// click to select
card.addEventListener('mousedown', e => {
// tap / click to select
card.addEventListener('pointerdown', e => {
if (e.target.closest('.fact') || e.target.closest('.resize-h')) return;
selectField(f.id);
});
@@ -661,28 +663,27 @@ function buildCard(f) {
card.querySelector('[data-act="dup"]').addEventListener('click', e => { e.stopPropagation(); dupField(f.id); });
card.querySelector('[data-act="del"]').addEventListener('click', e => { e.stopPropagation(); delField(f.id); });
// move via header
card.querySelector('.fcard-head').addEventListener('mousedown', e => {
// move via header (pointer events cover mouse + touch + stylus)
card.querySelector('.fcard-head').addEventListener('pointerdown', e => {
if (e.target.closest('.fact')) return;
e.preventDefault();
selectField(f.id);
const rect = surface.getBoundingClientRect();
const cRect = card.getBoundingClientRect();
const cRect = card.getBoundingClientRect();
moveState = {
id: f.id,
offX: e.clientX - cRect.left,
offY: e.clientY - cRect.top,
surfLeft: rect.left,
surfTop: rect.top,
};
card.setPointerCapture(e.pointerId); // keeps pointermove firing even if pointer leaves card
card.classList.add('moving');
showPreview(f);
});
// resize
card.querySelector('.resize-h').addEventListener('mousedown', e => {
// resize (pointer events cover mouse + touch + stylus)
card.querySelector('.resize-h').addEventListener('pointerdown', e => {
e.preventDefault(); e.stopPropagation();
selectField(f.id);
card.querySelector('.resize-h').setPointerCapture(e.pointerId);
resizeState = {
id: f.id,
startX: e.clientX, startY: e.clientY,
@@ -1051,8 +1052,8 @@ function dupField(id) {
// ═══════════════════════════════════════════════════════════════════════════
// MOUSE MOVE + RESIZE
// ═══════════════════════════════════════════════════════════════════════════
document.addEventListener('mousemove', e => {
// move ghost label
document.addEventListener('pointermove', e => {
// move ghost label (desktop drag only)
if (ghostEl.style.display !== 'none') {
ghostEl.style.left = (e.clientX + 12) + 'px';
ghostEl.style.top = (e.clientY + 10) + 'px';
@@ -1060,8 +1061,10 @@ document.addEventListener('mousemove', e => {
if (moveState) {
const f = fields.find(f => f.id === moveState.id); if (!f) return;
const x = e.clientX - moveState.surfLeft - moveState.offX;
const y = e.clientY - moveState.surfTop - moveState.offY;
// Recalculate rect live — accounts for canvas-wrap scroll during touch drag
const sr = surface.getBoundingClientRect();
const x = e.clientX - sr.left - moveState.offX;
const y = e.clientY - sr.top - moveState.offY;
const { col, row } = pxCell(x, y);
const newCol = Math.max(1, Math.min(COLS - f.colSpan + 1, col));
const newRow = Math.max(1, row);
@@ -1094,7 +1097,7 @@ document.addEventListener('mousemove', e => {
}
});
document.addEventListener('mouseup', () => {
document.addEventListener('pointerup', () => {
if (moveState) {
surface.querySelector(`[data-id="${moveState.id}"]`)?.classList.remove('moving');
hidePreview(); renderProperties(); moveState = null;
@@ -1106,6 +1109,7 @@ document.addEventListener('mouseup', () => {
// PALETTE DRAG → CANVAS (HTML5 drag API)
// ═══════════════════════════════════════════════════════════════════════════
document.querySelectorAll('.pal-item').forEach(item => {
// Desktop: HTML5 drag-to-canvas
item.addEventListener('dragstart', e => {
palDragType = item.dataset.ftype;
e.dataTransfer.setData('ftype', item.dataset.ftype);
@@ -1119,6 +1123,23 @@ document.querySelectorAll('.pal-item').forEach(item => {
ghostEl.style.display = 'none';
item.classList.remove('dragging-src');
});
// Touch / iPad: tap palette item to append field at next available row
// iOS Safari does not support HTML5 drag API, so drag-to-canvas never fires.
let _tapStart = null;
item.addEventListener('pointerdown', e => {
if (e.pointerType !== 'touch') return;
_tapStart = { x: e.clientX, y: e.clientY };
});
item.addEventListener('pointerup', e => {
if (e.pointerType !== 'touch' || !_tapStart) return;
const moved = Math.hypot(e.clientX - _tapStart.x, e.clientY - _tapStart.y);
_tapStart = null;
if (moved > 12) return; // swipe in palette strip — not a tap
const maxRow = fields.reduce((m, f) => Math.max(m, f.row + f.rowSpan - 1), 0);
addField(item.dataset.ftype, 1, maxRow + 1);
});
item.addEventListener('pointercancel', () => { _tapStart = null; });
});
document.addEventListener('dragover', e => {
@@ -1159,8 +1180,8 @@ surface.addEventListener('drop', e => {
addField(type, Math.max(1, Math.min(COLS - cs + 1, col)), Math.max(1, row));
});
// deselect on bare canvas click
surface.addEventListener('mousedown', e => {
// deselect on bare canvas tap/click
surface.addEventListener('pointerdown', e => {
if (e.target === surface || e.target.closest('.grid-hint')) {
selectedId = null;
surface.querySelectorAll('.fcard').forEach(c => c.classList.remove('sel'));