mnfst 0.5.158 → 0.5.160

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,15 +22,18 @@
22
22
  * estimate Initial per-row height in px (default 50), or { width, height }
23
23
  * for galleries. Used for unmeasured rows; closer = less drift.
24
24
  * overscan Rows/lines to render above/below the window (default 3).
25
- * mode 'table' | 'grid' | 'flex' | 'block'. Auto-detected if omitted.
25
+ * mode 'rows' | 'gallery' | 'masonry'. Auto-detected if omitted
26
+ * (masonry must be set explicitly).
26
27
  *
27
- * Modes:
28
- * block — one item per line, full width (default for stacked divs).
29
- * table native <table>; spacer is a <tr>. One item per line.
30
- * grid — .grid-table (display:grid + display:contents rows); spacer spans
31
- * grid-column 1 / -1. One logical row per line.
32
- * flex — flex-wrap gallery; items pack N per line, whole lines render so
33
- * flex sizing stays correct. Ragged width + height supported.
28
+ * Modes (how items fill lines — the spacer element is auto-detected separately):
29
+ * rows — one item per line: stacked lists, native <table>, and
30
+ * .grid-table. The spacer is a div, a <tr>, or a grid cell
31
+ * depending on the container.
32
+ * gallery — many items per line (flex-wrap); items pack N per line and
33
+ * whole lines render so flex sizing stays correct. Ragged
34
+ * width + height supported.
35
+ * masonry — independent-size items packed into the shortest column
36
+ * (absolute positioning); set explicitly with mode: 'masonry'.
34
37
  *
35
38
  * Notes:
36
39
  * - Column widths should be content-independent (table-layout: fixed, fixed/%
@@ -81,8 +84,9 @@ function initializeVirtualPlugin() {
81
84
  const cs = getComputedStyle(scrollEl);
82
85
  if (cs.overflow === 'visible' && cs.overflowY === 'visible') scrollEl.style.overflow = 'auto';
83
86
 
84
- const mode = options.mode || detectMode(host);
85
- const isFlex = mode === 'flex';
87
+ const kind = detectKind(host); // spacer/geometry: table | grid | flex | block
88
+ const mode = options.mode || detectMode(kind, host); // windowing: rows | gallery | masonry
89
+ const isGallery = mode === 'gallery';
86
90
  const isMasonry = mode === 'masonry';
87
91
 
88
92
  // Masonry options: fixed `columns` or target `columnWidth`; optional
@@ -108,8 +112,8 @@ function initializeVirtualPlugin() {
108
112
  sizer.style.cssText = 'position:absolute;top:0;left:0;width:1px;height:0;pointer-events:none';
109
113
  host.appendChild(sizer);
110
114
  } else {
111
- topSpacer = makeSpacer(mode, colCount);
112
- botSpacer = makeSpacer(mode, colCount);
115
+ topSpacer = makeSpacer(kind, colCount);
116
+ botSpacer = makeSpacer(kind, colCount);
113
117
  host.insertBefore(topSpacer, template);
114
118
  host.insertBefore(botSpacer, template);
115
119
  }
@@ -117,12 +121,12 @@ function initializeVirtualPlugin() {
117
121
 
118
122
  // --- State ---
119
123
  const heights = new Map(); // key -> measured line/row height
120
- const widths = new Map(); // key -> measured item width (flex only)
124
+ const widths = new Map(); // key -> measured item width (gallery only)
121
125
  let measuredSum = 0, measuredCount = 0;
122
126
  const rendered = new Map(); // key -> { node, scope, removeScope }
123
127
  let data = [];
124
128
  let cumulative = new Float64Array(1); // line-top offsets; last = total
125
- let lines = []; // flex: [{ start, end, height }]
129
+ let lines = []; // gallery: [{ start, end, height }]
126
130
  let pos = []; // masonry: per-index { x, y, w, h }
127
131
  let sortedTop = []; // masonry: indices sorted by y
128
132
  let totalHeight = 0; // masonry: packed content height
@@ -139,7 +143,7 @@ function initializeVirtualPlugin() {
139
143
 
140
144
  function rebuild() {
141
145
  if (isMasonry) rebuildMasonry();
142
- else if (isFlex) rebuildFlex();
146
+ else if (isGallery) rebuildGallery();
143
147
  else rebuildSingle();
144
148
  }
145
149
 
@@ -189,7 +193,7 @@ function initializeVirtualPlugin() {
189
193
  cumulative[n] = y;
190
194
  }
191
195
 
192
- function rebuildFlex() {
196
+ function rebuildGallery() {
193
197
  const cw = contentWidth();
194
198
  const gx = gapX(), gy = gapY();
195
199
  lines = [];
@@ -234,7 +238,7 @@ function initializeVirtualPlugin() {
234
238
 
235
239
  function render() {
236
240
  if (isMasonry) return renderMasonry();
237
- const total = isFlex ? lines.length : data.length;
241
+ const total = isGallery ? lines.length : data.length;
238
242
  if (!total) { clearRows(); setHeight(topSpacer, 0); setHeight(botSpacer, 0); return; }
239
243
  const vh = scrollEl.clientHeight;
240
244
  if (!vh) return; // defer until sized
@@ -250,8 +254,8 @@ function initializeVirtualPlugin() {
250
254
  setHeight(botSpacer, cumulative[total] - cumulative[endUnit]);
251
255
 
252
256
  // Resolve the item range for these units.
253
- const startItem = isFlex ? lines[startUnit].start : startUnit;
254
- const endItem = isFlex ? lines[endUnit - 1].end : endUnit;
257
+ const startItem = isGallery ? lines[startUnit].start : startUnit;
258
+ const endItem = isGallery ? lines[endUnit - 1].end : endUnit;
255
259
 
256
260
  const visible = new Set();
257
261
  const keys = [];
@@ -351,7 +355,7 @@ function initializeVirtualPlugin() {
351
355
  // Only measure rows we haven't sized yet.
352
356
  const pending = [];
353
357
  for (const key of keys) {
354
- const need = isFlex ? (!heights.has(key) || !widths.has(key)) : !heights.has(key);
358
+ const need = isGallery ? (!heights.has(key) || !widths.has(key)) : !heights.has(key);
355
359
  if (need && rendered.has(key)) pending.push(key);
356
360
  }
357
361
  if (!pending.length || measureScheduled) return;
@@ -380,7 +384,7 @@ function initializeVirtualPlugin() {
380
384
  heights.set(key, h);
381
385
  changed = true;
382
386
  }
383
- if (isFlex) {
387
+ if (isGallery) {
384
388
  const w = node.offsetWidth;
385
389
  if (w && widths.get(key) !== w) { widths.set(key, w); changed = true; }
386
390
  }
@@ -471,7 +475,8 @@ function initializeVirtualPlugin() {
471
475
 
472
476
  // ---- Module helpers ----
473
477
 
474
- function detectMode(host) {
478
+ // Container kind — which spacer element + geometry the container needs.
479
+ function detectKind(host) {
475
480
  const tag = host.tagName;
476
481
  if (tag === 'TBODY' || tag === 'THEAD' || tag === 'TFOOT' || tag === 'TABLE') return 'table';
477
482
  const disp = getComputedStyle(host).display;
@@ -480,9 +485,15 @@ function detectMode(host) {
480
485
  return 'block';
481
486
  }
482
487
 
483
- function makeSpacer(mode, colCount) {
488
+ // Windowing mode how items fill lines. Masonry is never auto-detected.
489
+ function detectMode(kind, host) {
490
+ if (kind === 'flex' && (getComputedStyle(host).flexWrap || '').startsWith('wrap')) return 'gallery';
491
+ return 'rows';
492
+ }
493
+
494
+ function makeSpacer(kind, colCount) {
484
495
  let s;
485
- if (mode === 'table') {
496
+ if (kind === 'table') {
486
497
  s = document.createElement('tr');
487
498
  const td = document.createElement('td');
488
499
  if (colCount > 1) td.colSpan = colCount;
@@ -490,8 +501,8 @@ function makeSpacer(mode, colCount) {
490
501
  s.appendChild(td);
491
502
  } else {
492
503
  s = document.createElement('div');
493
- if (mode === 'grid') s.style.gridColumn = '1 / -1';
494
- else if (mode === 'flex') s.style.flex = '0 0 100%';
504
+ if (kind === 'grid') s.style.gridColumn = '1 / -1';
505
+ else if (kind === 'flex') s.style.flex = '0 0 100%';
495
506
  s.style.width = '100%';
496
507
  }
497
508
  s.dataset.virtualSpacer = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.158",
3
+ "version": "0.5.160",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",