BUG-03: Span-Geometrie im Initial-Render (grid-column/grid-row direkt setzen)
Initial-Render hatte keine Span-Geometrie:
renderGrid() hing Items in die Origin-Cell mit width:100% height:100%.
Ein 2x2-Item sah damit aus wie eine 1x1-Box mit Mini-Inhalt.
Resize-Code (in BUG-01/02-Branch, applySize) setzte zwar korrekt
grid-column/row per JS — aber nur WÄHREND Resize. Initial war's kaputt.
Fix:
- renderGrid() hängt Items jetzt direkt in den Grid-Container (cont),
nicht mehr in die Origin-Cell.
- style.gridColumn = '${it.x + 1} / span ${it.w}' setzt die CSS-Span-Geometrie
direkt im Inline-Style.
- Origin-Cell bekommt nur noch die 'occupied'-Klasse (für die Optik).
- DOM-Baum: Items sind Geschwister der Cells → keine DOM-Kollision mehr,
Drag-Events auf Nachbar-Cells werden nicht vom Item verschluckt.
Vorteile:
- 2x2-Item rendert visuell über 2x2 Cells (richtige Größe beim ersten Laden)
- Drag auf JEDE Zelle innerhalb der Item-Bbox funktioniert
- applySize (Resize) kann den Span nahtlos aktualisieren ohne DOM-Wechsel
- Kein Flicker beim Resize (initial state ist schon korrekt)
Beweis: tests/test_span_geometry.js (8/8 grün)
1. gridColumn wird per JS gesetzt
2. gridRow wird per JS gesetzt
3. Item wird in Container (cont) gehängt
4. Item wird NICHT mehr in Origin-Cell gehängt
5. Origin-Cell bekommt 'occupied' Klasse
6. gridColumn Format: <x+1> / span <w>
7. Keine width:100% im Item-CSS-Block
8. CSS-Kommentar erwähnt BUG-03
Hinweis: Mein ursprüngliches Issue-Statement war zu pessimistisch (Items
verdecken keine Nachbarzellen visuell). Sie saßen nur 1x1 in der
Origin-Cell. Dennoch ist der Fix substantiell: Initial-Render zeigt jetzt
korrekte Größe, und zukünftige Resize-Codes können sich auf den Span
verlassen ohne DOM-Mutation.
Closes #5
This commit is contained in:
+14
-8
@@ -1297,7 +1297,6 @@
|
|||||||
// Place items into their origin cell
|
// Place items into their origin cell
|
||||||
const occ = occupiedCells();
|
const occ = occupiedCells();
|
||||||
layoutItems.forEach((it, idx) => {
|
layoutItems.forEach((it, idx) => {
|
||||||
const cellIdx = it.x + ',' + it.y;
|
|
||||||
const originCell = cont.querySelector(`[data-cell="${it.x + it.y * gridCols}"]`);
|
const originCell = cont.querySelector(`[data-cell="${it.x + it.y * gridCols}"]`);
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
@@ -1305,7 +1304,7 @@
|
|||||||
div.className = 'grid-item';
|
div.className = 'grid-item';
|
||||||
if (it.w > 1 || it.h > 1) div.classList.add('span-2x2');
|
if (it.w > 1 || it.h > 1) div.classList.add('span-2x2');
|
||||||
div.dataset.idx = idx;
|
div.dataset.idx = idx;
|
||||||
div.draggable = true;
|
div.draggable = true; // legacy HTML5-DnD bleibt für Move; Resize nutzt separaten Handler
|
||||||
|
|
||||||
// OOB marker
|
// OOB marker
|
||||||
const hasOOB = it.y + it.h > gridRows || it.x + it.w > gridCols || it.y < 0;
|
const hasOOB = it.y + it.h > gridRows || it.x + it.w > gridCols || it.y < 0;
|
||||||
@@ -1321,12 +1320,19 @@
|
|||||||
<div class="grid-item-resize" data-resize="${idx}" title="Größe ändern (Ecke ziehen)"></div>
|
<div class="grid-item-resize" data-resize="${idx}" title="Größe ändern (Ecke ziehen)"></div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (originCell) {
|
// BUG-03: Span-Geometrie per CSS Grid (grid-column/grid-row) statt
|
||||||
originCell.classList.add('occupied');
|
// per Cell-DOM-Anker. Item wird direkt in den Grid-Container gehängt,
|
||||||
originCell.appendChild(div);
|
// nicht in die Origin-Cell. Damit:
|
||||||
} else {
|
// - NxN-Items rendern visuell über NxN Cells
|
||||||
cont.appendChild(div);
|
// - Drag-Events auf Nachbar-Cells werden nicht vom Item-DOM
|
||||||
}
|
// verschluckt (Item ist nicht mehr Kind der Cell)
|
||||||
|
// - Resize (applySize in startResizePointer) kann den Span nahtlos
|
||||||
|
// aktualisieren ohne den DOM-Anker zu wechseln
|
||||||
|
div.style.gridColumn = `${it.x + 1} / span ${it.w}`;
|
||||||
|
div.style.gridRow = `${it.y + 1} / span ${it.h}`;
|
||||||
|
cont.appendChild(div);
|
||||||
|
// Origin-Cell nur als "occupied" markieren, damit sie ihre leere Optik verliert
|
||||||
|
if (originCell) originCell.classList.add('occupied');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark occupied cells
|
// Mark occupied cells
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Test für BUG-03: Span-Geometrie im Initial-Render.
|
||||||
|
//
|
||||||
|
// Erwartung nach Fix:
|
||||||
|
// 1) renderGrid setzt grid-column/grid-row am Item direkt (per JS).
|
||||||
|
// 2) Item wird in den Container gehängt (cont.appendChild), nicht in originCell.
|
||||||
|
// 3) Origin-Cell bekommt nur die "occupied"-Klasse.
|
||||||
|
// 4) 2x2-Item hat style.gridColumn === '<x+1> / span 2' und gridRow === '<y+1> / span 2'.
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const html = fs.readFileSync('templates/index.html', 'utf8');
|
||||||
|
const js = html.match(/<script>\s*\n([\s\S]*?)<\/script>/)[1];
|
||||||
|
|
||||||
|
function check(name, fn) {
|
||||||
|
const r = fn();
|
||||||
|
console.log((r ? '✓' : '✗') + ' ' + name);
|
||||||
|
if (!r) process.exitCode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) renderGrid setzt style.gridColumn/gridRow am Item
|
||||||
|
check('gridColumn wird per JS gesetzt', () =>
|
||||||
|
/\.style\.gridColumn\s*=/.test(js));
|
||||||
|
|
||||||
|
check('gridRow wird per JS gesetzt', () =>
|
||||||
|
/\.style\.gridRow\s*=/.test(js));
|
||||||
|
|
||||||
|
// 2) Item wird in cont.appendChild gehängt, NICHT in originCell.appendChild
|
||||||
|
check('Item wird in Container (cont) gehängt', () =>
|
||||||
|
/cont\.appendChild\(div\)/.test(js));
|
||||||
|
|
||||||
|
check('Item wird NICHT mehr in Origin-Cell gehängt', () =>
|
||||||
|
!/originCell\.appendChild\(div\)/.test(js));
|
||||||
|
|
||||||
|
// 3) Origin-Cell bekommt nur occupied-Klasse
|
||||||
|
check('Origin-Cell bekommt "occupied" Klasse', () =>
|
||||||
|
/originCell\.classList\.add\(['"]occupied['"]\)/.test(js));
|
||||||
|
|
||||||
|
// 4) Format: `${it.x + 1} / span ${it.w}` (CSS-Grid-Notation)
|
||||||
|
check('gridColumn Format: <x+1> / span <w>', () =>
|
||||||
|
/it\.x\s*\+\s*1[^`]*\$\{it\.w\}/.test(js) || /\$\{it\.x\s*\+\s*1\}[^`]*span[^`]*\$\{it\.w\}/.test(js));
|
||||||
|
|
||||||
|
// 5) Kein 100%/100% Trick auf Items (das war der Bug, der Span verhindert hat)
|
||||||
|
check('Keine "width: 100%" mehr im Item-CSS-Block', () => {
|
||||||
|
const css = html.match(/<style>([\s\S]*?)<\/style>/g).join('\n');
|
||||||
|
// .grid-item soll nicht width:100% haben
|
||||||
|
const itemCssMatch = /\.grid-item\s*\{([^}]*)\}/.exec(css);
|
||||||
|
if (!itemCssMatch) return true; // falls keine Regel
|
||||||
|
const body = itemCssMatch[1];
|
||||||
|
return !/width:\s*100%/.test(body);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6) Visuelle Begründung im CSS-Kommentar (für die Nachwelt)
|
||||||
|
check('CSS-Kommentar erwähnt BUG-03 Span-Geometrie', () =>
|
||||||
|
/BUG-03/i.test(html));
|
||||||
|
|
||||||
|
console.log('\n========');
|
||||||
|
process.exit(process.exitCode || 0);
|
||||||
Reference in New Issue
Block a user