05/26 Update form editor
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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'));
|
||||
|
||||
Reference in New Issue
Block a user