yummy-guide-generic-administrate 0.8.0 → 0.8.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b23f3b7fd7d17420fdeaf7325f51f1f70b93fb86497bcdb6621b617b5086d926
4
- data.tar.gz: 38004b14e38a03b882d6e84253fc63ad1a2e8480ab287ee27ced1d13e81239a7
3
+ metadata.gz: e0c5692ebb60bfcc4d51157cdca7e52f974523f546c0d75ab5e3b350815a5316
4
+ data.tar.gz: 9f398e159619da8c35955fcaaa0a2d2c7a1df5249154486c95326a5fe99e6b1d
5
5
  SHA512:
6
- metadata.gz: ecc7d99ccc8f23f5b4d0dd9288a9d6f870f95dc9c2d19256865828fa9a3d1f10d322fd37d627d2e85d648e13ab1fb128fb2d0430035451147fa2f4b72739bbb4
7
- data.tar.gz: 05b2097517ad48edd1e8f54230bd3f2d1ea5e591dccc733d0397e1ec89d7d3b855920066296b4119c6c4b7b46572413ca07730dae2663e584cf403d1bc0f6cc9
6
+ metadata.gz: 92ea6916e4083ad8dc0301f48df920d11f63a7f028846fbbdd6d9886b7b29eb30f0e812e2fa413b8db23de600050460867ec9a93d0d799124032619cc552b3fb
7
+ data.tar.gz: 688558efd455e8a364b0a7a070e2f088fdd4adb6684a4b143e37438cd8a5d8192f49918d834afba7478851b075c1984570e5d6666bdd04850f229167102a03fe
@@ -4,21 +4,24 @@
4
4
  var HEADER_CLASS = 'admin-column-resizer__header';
5
5
  var TABLE_CLASS = 'admin-column-resizer__table';
6
6
  var DRAGGING_BODY_CLASS = 'admin-column-resizer--dragging';
7
+ var APPLYING_BODY_CLASS = 'admin-column-resizer--applying';
8
+ var PREVIEW_CLASS = 'admin-column-resizer__preview';
7
9
  var FIXED_HEADER_TABLE_CLASS = 'table-fixed-header__table';
10
+ var ADJUSTED_COLUMNS_ATTRIBUTE = 'data-admin-column-resizer-adjusted-columns';
8
11
  var STORAGE_PREFIX = 'yummyGuideAdminColumnWidths:v1:';
9
12
  var STYLE_ELEMENT_ID = 'admin-column-resizer-rules';
10
13
  var WIDTH_VAR_PREFIX = '--admin-column-resizer-col-';
11
14
  var MIN_WIDTH = 48;
12
- var STICKY_REFRESH_INTERVAL = 120;
13
15
 
14
16
  var dragState = null;
15
- var dragApplyFrame = null;
17
+ var dragPreviewFrame = null;
18
+ var widthApplyFrame = null;
16
19
  var generatedRuleCount = 0;
17
20
  var initializedHandles = new WeakSet();
18
21
  var tableStates = new WeakMap();
19
22
  var stickyRefreshFrame = null;
20
- var stickyRefreshTimer = null;
21
- var lastStickyRefreshAt = 0;
23
+ var stickyRefreshCallbacks = [];
24
+ var applyingWidth = false;
22
25
 
23
26
  function storageScopeForTable(table) {
24
27
  var sourceTable = matchingSourceTable(table);
@@ -74,6 +77,10 @@
74
77
  return preciseNumber(rectWidth || element.offsetWidth || 0);
75
78
  }
76
79
 
80
+ function viewportHeight() {
81
+ return window.innerHeight || document.documentElement.clientHeight || 0;
82
+ }
83
+
77
84
  function normalizedIdentifier(value) {
78
85
  return (value || '').toString().trim().toLowerCase()
79
86
  .replace(/[^a-z0-9]+/g, '_')
@@ -169,13 +176,22 @@
169
176
  function columnRule(index) {
170
177
  var nthChild = index + 1;
171
178
  var variableName = columnWidthVariable(index);
172
- var selector = [
173
- '.' + TABLE_CLASS + ' > thead > tr > :nth-child(' + nthChild + ')',
174
- '.' + TABLE_CLASS + ' > tbody > tr > :nth-child(' + nthChild + ')',
175
- '.' + TABLE_CLASS + ' > tfoot > tr > :nth-child(' + nthChild + ')'
176
- ].join(', ');
179
+ var adjustedTableSelector = '.' + TABLE_CLASS + '[' + ADJUSTED_COLUMNS_ATTRIBUTE + '~="' + nthChild + '"]';
180
+ var selectors = [
181
+ adjustedTableSelector + ' > thead > tr > :nth-child(' + nthChild + ')',
182
+ adjustedTableSelector + ' > tbody > tr > :nth-child(' + nthChild + ')',
183
+ adjustedTableSelector + ' > tfoot > tr > :nth-child(' + nthChild + ')'
184
+ ];
185
+ var selector = selectors.join(', ');
186
+
187
+ var contentSelector = selectors.concat(selectors.map(function(cellSelector) {
188
+ return cellSelector + ' *';
189
+ })).join(', ');
177
190
 
178
- return selector + ' { box-sizing: border-box !important; width: var(' + variableName + ') !important; min-width: var(' + variableName + ') !important; max-width: var(' + variableName + ') !important; }';
191
+ return [
192
+ selector + ' { box-sizing: border-box !important; width: var(' + variableName + ') !important; min-width: var(' + variableName + ') !important; max-width: var(' + variableName + ') !important; }',
193
+ contentSelector + ' { white-space: normal !important; overflow-wrap: anywhere !important; word-break: break-word !important; }'
194
+ ].join('\n');
179
195
  }
180
196
 
181
197
  function ensureStyleElement() {
@@ -315,6 +331,33 @@
315
331
  colgroup.children[index].style.width = widthValue;
316
332
  }
317
333
 
334
+ function adjustedColumnIndexes(table) {
335
+ return (table.getAttribute(ADJUSTED_COLUMNS_ATTRIBUTE) || '')
336
+ .split(/\s+/)
337
+ .filter(Boolean);
338
+ }
339
+
340
+ function setAdjustedColumn(table, index, adjusted) {
341
+ var indexToken = (index + 1).toString();
342
+ var indexes = adjustedColumnIndexes(table).filter(function(candidate, candidateIndex, candidates) {
343
+ return candidate !== indexToken && candidates.indexOf(candidate) === candidateIndex;
344
+ });
345
+
346
+ if (adjusted) {
347
+ indexes.push(indexToken);
348
+ indexes.sort(function(left, right) {
349
+ return parseInt(left, 10) - parseInt(right, 10);
350
+ });
351
+ }
352
+
353
+ if (indexes.length === 0) {
354
+ table.removeAttribute(ADJUSTED_COLUMNS_ATTRIBUTE);
355
+ return;
356
+ }
357
+
358
+ table.setAttribute(ADJUSTED_COLUMNS_ATTRIBUTE, indexes.join(' '));
359
+ }
360
+
318
361
  function clearColgroupWidth(table, columnCount, index) {
319
362
  var colgroup = ensureManagedColgroup(table, columnCount);
320
363
  if (!colgroup || !colgroup.children[index]) return;
@@ -329,6 +372,7 @@
329
372
 
330
373
  var widthValue = cssPixelValue(width);
331
374
  table.style.setProperty(columnWidthVariable(index), widthValue);
375
+ setAdjustedColumn(table, index, true);
332
376
  applyColgroupWidth(table, state.columnCount, index, widthValue);
333
377
  }
334
378
 
@@ -338,6 +382,7 @@
338
382
  if (!state || index === undefined) return;
339
383
 
340
384
  table.style.removeProperty(columnWidthVariable(index));
385
+ setAdjustedColumn(table, index, false);
341
386
  clearColgroupWidth(table, state.columnCount, index);
342
387
  }
343
388
 
@@ -381,26 +426,23 @@
381
426
 
382
427
  function dispatchStickyRefresh() {
383
428
  stickyRefreshFrame = null;
384
- lastStickyRefreshAt = Date.now();
385
429
  window.dispatchEvent(new Event('resize'));
386
- }
387
430
 
388
- function scheduleStickyRefresh(immediate) {
389
- if (stickyRefreshFrame || stickyRefreshTimer) return;
431
+ var callbacks = stickyRefreshCallbacks;
432
+ stickyRefreshCallbacks = [];
433
+ callbacks.forEach(function(callback) {
434
+ callback();
435
+ });
436
+ }
390
437
 
391
- var elapsed = Date.now() - lastStickyRefreshAt;
392
- var delay = immediate ? 0 : Math.max(0, STICKY_REFRESH_INTERVAL - elapsed);
438
+ function scheduleStickyRefresh(callback) {
439
+ if (callback) {
440
+ stickyRefreshCallbacks.push(callback);
441
+ }
393
442
 
394
- stickyRefreshTimer = window.setTimeout(function() {
395
- stickyRefreshTimer = null;
396
- stickyRefreshFrame = window.requestAnimationFrame(dispatchStickyRefresh);
397
- }, delay);
398
- }
443
+ if (stickyRefreshFrame) return;
399
444
 
400
- function columnNeedsDragRefresh(header) {
401
- return header.classList.contains('sticky') ||
402
- header.classList.contains('sticky-left') ||
403
- header.classList.contains('actions-column');
445
+ stickyRefreshFrame = window.requestAnimationFrame(dispatchStickyRefresh);
404
446
  }
405
447
 
406
448
  function sourceTableForHandle(handle) {
@@ -419,34 +461,191 @@
419
461
  }) || matchingSourceTable(table) || null;
420
462
  }
421
463
 
422
- function flushDragWidth() {
464
+ function previewBoundsForTable(table, header) {
465
+ var headerRect = header.getBoundingClientRect();
466
+ var tableRect = table.getBoundingClientRect();
467
+ var scrollContainer = table.closest('.sticky-table-scroll, .scroll-table, .home-table__wrapper');
468
+ var scrollRect = scrollContainer ? scrollContainer.getBoundingClientRect() : tableRect;
469
+ var headerTable = header.closest(TABLE_SELECTOR);
470
+ var fromFixedHeader = headerTable && headerTable.classList.contains(FIXED_HEADER_TABLE_CLASS);
471
+ var viewportBottom = viewportHeight();
472
+ var top = fromFixedHeader ? headerRect.top : Math.max(tableRect.top, scrollRect.top);
473
+ var bottom = Math.min(viewportBottom, Math.max(tableRect.bottom, scrollRect.bottom, headerRect.bottom));
474
+
475
+ top = Math.max(0, Math.min(top, viewportBottom));
476
+ if (bottom <= top) {
477
+ bottom = Math.min(viewportBottom, top + Math.max(headerRect.height, 32));
478
+ }
479
+
480
+ return {
481
+ left: headerRect.left,
482
+ top: top,
483
+ height: Math.max(32, bottom - top)
484
+ };
485
+ }
486
+
487
+ function updateDragPreview(preview, width) {
488
+ if (!preview) return;
489
+
490
+ preview.element.style.width = cssPixelValue(width);
491
+ }
492
+
493
+ function createDragPreview(table, header, width) {
494
+ var bounds = previewBoundsForTable(table, header);
495
+ var element = document.createElement('div');
496
+
497
+ element.className = PREVIEW_CLASS;
498
+ element.setAttribute('aria-hidden', 'true');
499
+ element.style.left = cssPixelValue(bounds.left);
500
+ element.style.top = cssPixelValue(bounds.top);
501
+ element.style.height = cssPixelValue(bounds.height);
502
+
503
+ document.body.appendChild(element);
504
+
505
+ var preview = {
506
+ element: element
507
+ };
508
+ updateDragPreview(preview, width);
509
+
510
+ return preview;
511
+ }
512
+
513
+ function removePreview(preview) {
514
+ if (!preview) return;
515
+
516
+ preview.element.remove();
517
+ }
518
+
519
+ function removeDragPreview() {
520
+ if (!dragState || !dragState.preview) return;
521
+
522
+ removePreview(dragState.preview);
523
+ dragState.preview = null;
524
+ }
525
+
526
+ function flushDragPreview() {
423
527
  if (!dragState) return;
424
528
 
425
- if (dragApplyFrame) {
426
- window.cancelAnimationFrame(dragApplyFrame);
427
- dragApplyFrame = null;
529
+ if (dragPreviewFrame) {
530
+ window.cancelAnimationFrame(dragPreviewFrame);
531
+ dragPreviewFrame = null;
532
+ }
533
+
534
+ updateDragPreview(dragState.preview, dragState.currentWidth || dragState.startWidth);
535
+ }
536
+
537
+ function releaseDragPointerCapture() {
538
+ if (!dragState || !dragState.handle || dragState.pointerId === null || typeof dragState.pointerId === 'undefined') return;
539
+ if (!dragState.handle.releasePointerCapture) return;
540
+
541
+ try {
542
+ dragState.handle.releasePointerCapture(dragState.pointerId);
543
+ } catch (_error) {
544
+ // The pointer may already be released by the browser after cancellation.
428
545
  }
546
+ }
547
+
548
+ function captureDragPointer(handle, event) {
549
+ if (!handle.setPointerCapture || event.pointerId === null || typeof event.pointerId === 'undefined') return;
429
550
 
430
- applyColumnWidth(dragState.columnId, dragState.currentWidth || dragState.startWidth, dragState.storageKey);
551
+ try {
552
+ handle.setPointerCapture(event.pointerId);
553
+ } catch (_error) {
554
+ // Pointer capture is an enhancement for touch/pen dragging; document listeners remain as fallback.
555
+ }
556
+ }
557
+
558
+ function eventMatchesDragPointer(event) {
559
+ return !event ||
560
+ dragState.pointerId === null ||
561
+ typeof dragState.pointerId === 'undefined' ||
562
+ event.pointerId === dragState.pointerId;
563
+ }
564
+
565
+ function pointerCanStartDrag(event) {
566
+ if (event.isPrimary === false) return false;
567
+
568
+ return event.pointerType !== 'mouse' || event.button === 0;
431
569
  }
432
570
 
433
571
  function scheduleDragWidth(width) {
434
572
  dragState.currentWidth = width;
435
573
  dragState.moved = dragState.moved || Math.abs(width - dragState.startWidth) > 2;
436
574
 
437
- if (dragApplyFrame) return;
575
+ if (dragPreviewFrame) return;
438
576
 
439
- dragApplyFrame = window.requestAnimationFrame(function() {
440
- dragApplyFrame = null;
577
+ dragPreviewFrame = window.requestAnimationFrame(function() {
578
+ dragPreviewFrame = null;
441
579
  if (!dragState) return;
442
580
 
443
- applyColumnWidth(dragState.columnId, dragState.currentWidth, dragState.storageKey);
444
- if (dragState.refreshDuringDrag) scheduleStickyRefresh(false);
581
+ updateDragPreview(dragState.preview, dragState.currentWidth);
582
+ });
583
+ }
584
+
585
+ function refreshStickyHeaderColumn(pendingWidth, callback) {
586
+ var api = window.YummyGuideAdministrateStickyTableHeaders;
587
+
588
+ if (api && typeof api.refreshColumnWidth === 'function') {
589
+ var refreshed = api.refreshColumnWidth({
590
+ sourceTable: pendingWidth.sourceTable,
591
+ columnId: pendingWidth.columnId,
592
+ width: pendingWidth.width
593
+ });
594
+
595
+ if (refreshed) {
596
+ window.requestAnimationFrame(callback);
597
+ return;
598
+ }
599
+ }
600
+
601
+ scheduleStickyRefresh(callback);
602
+ }
603
+
604
+ function refreshStickyLeftColumns(sourceTable) {
605
+ var api = window.YummyGuideAdministrateStickyLeftColumns;
606
+
607
+ if (api && typeof api.refreshTable === 'function') {
608
+ api.refreshTable(sourceTable);
609
+ }
610
+ }
611
+
612
+ function stopApplyingWidth(preview) {
613
+ removePreview(preview);
614
+ applyingWidth = false;
615
+ document.body.classList.remove(APPLYING_BODY_CLASS);
616
+ }
617
+
618
+ function applyPendingWidth(pendingWidth) {
619
+ try {
620
+ var widths = safeReadWidths(pendingWidth.storageKey);
621
+ applyColumnWidth(pendingWidth.columnId, pendingWidth.width, pendingWidth.storageKey);
622
+ widths[pendingWidth.columnId] = preciseNumber(pendingWidth.width);
623
+ safeWriteWidths(pendingWidth.storageKey, widths);
624
+ refreshStickyLeftColumns(pendingWidth.sourceTable);
625
+ refreshStickyHeaderColumn(pendingWidth, function() {
626
+ stopApplyingWidth(pendingWidth.preview);
627
+ });
628
+ } catch (error) {
629
+ stopApplyingWidth(pendingWidth.preview);
630
+ throw error;
631
+ }
632
+ }
633
+
634
+ function schedulePendingWidthApply(pendingWidth) {
635
+ applyingWidth = true;
636
+ document.body.classList.add(APPLYING_BODY_CLASS);
637
+
638
+ widthApplyFrame = window.requestAnimationFrame(function() {
639
+ widthApplyFrame = window.requestAnimationFrame(function() {
640
+ widthApplyFrame = null;
641
+ applyPendingWidth(pendingWidth);
642
+ });
445
643
  });
446
644
  }
447
645
 
448
646
  function startDrag(event) {
449
- if (event.button !== 0) return;
647
+ if (!pointerCanStartDrag(event)) return;
648
+ if (applyingWidth || widthApplyFrame) return;
450
649
 
451
650
  var handle = event.currentTarget;
452
651
  var header = handle.closest('th');
@@ -459,8 +658,11 @@
459
658
  if (!sourceTable) return;
460
659
 
461
660
  var sourceHeader = columnHeader(sourceTable, columnId) || header;
462
- var startWidth = measuredWidth(sourceHeader) || measuredWidth(header);
661
+ var sourceHeaderWidth = measuredWidth(sourceHeader);
662
+ var handleHeaderWidth = measuredWidth(header);
663
+ var startWidth = sourceHeaderWidth || handleHeaderWidth;
463
664
  if (!startWidth) return;
665
+ var previewHeader = sourceHeaderWidth ? sourceHeader : header;
464
666
 
465
667
  event.preventDefault();
466
668
  event.stopPropagation();
@@ -471,10 +673,14 @@
471
673
  startX: event.clientX,
472
674
  startWidth: startWidth,
473
675
  currentWidth: startWidth,
676
+ pointerId: event.pointerId,
677
+ handle: handle,
678
+ sourceTable: sourceTable,
474
679
  moved: false,
475
- refreshDuringDrag: columnNeedsDragRefresh(sourceHeader)
680
+ preview: createDragPreview(sourceTable, previewHeader, startWidth)
476
681
  };
477
682
 
683
+ captureDragPointer(handle, event);
478
684
  document.body.classList.add(DRAGGING_BODY_CLASS);
479
685
  document.addEventListener('pointermove', handleDragMove);
480
686
  document.addEventListener('pointerup', finishDrag);
@@ -483,6 +689,7 @@
483
689
 
484
690
  function handleDragMove(event) {
485
691
  if (!dragState) return;
692
+ if (!eventMatchesDragPointer(event)) return;
486
693
 
487
694
  event.preventDefault();
488
695
 
@@ -491,24 +698,29 @@
491
698
 
492
699
  function finishDrag(event) {
493
700
  if (!dragState) return;
701
+ if (!eventMatchesDragPointer(event)) return;
494
702
 
495
703
  if (event) {
496
704
  event.preventDefault();
497
705
  }
498
706
 
499
- flushDragWidth();
707
+ flushDragPreview();
500
708
 
501
- var widths = safeReadWidths(dragState.storageKey);
502
- var width = Math.max(MIN_WIDTH, dragState.currentWidth || dragState.startWidth);
503
- widths[dragState.columnId] = preciseNumber(width);
504
- safeWriteWidths(dragState.storageKey, widths);
709
+ var pendingWidth = {
710
+ columnId: dragState.columnId,
711
+ storageKey: dragState.storageKey,
712
+ sourceTable: dragState.sourceTable,
713
+ width: Math.max(MIN_WIDTH, dragState.currentWidth || dragState.startWidth),
714
+ preview: dragState.preview
715
+ };
505
716
 
717
+ releaseDragPointerCapture();
506
718
  dragState = null;
507
719
  document.body.classList.remove(DRAGGING_BODY_CLASS);
508
720
  document.removeEventListener('pointermove', handleDragMove);
509
721
  document.removeEventListener('pointerup', finishDrag);
510
722
  document.removeEventListener('pointercancel', finishDrag);
511
- scheduleStickyRefresh(true);
723
+ schedulePendingWidthApply(pendingWidth);
512
724
  }
513
725
 
514
726
  function resetColumn(event) {
@@ -528,7 +740,7 @@
528
740
  delete widths[columnId];
529
741
  safeWriteWidths(key, widths);
530
742
  clearColumnWidth(columnId, key);
531
- scheduleStickyRefresh(true);
743
+ scheduleStickyRefresh();
532
744
  }
533
745
 
534
746
  function stopHandleClick(event) {
@@ -2,6 +2,7 @@
2
2
  var TABLE_SELECTOR = "table[data-fixed-columns-count]";
3
3
  var MOBILE_MEDIA_QUERY = "(max-width: 767px)";
4
4
  var resizeObservers = new WeakMap();
5
+ var suppressedResizeTables = new WeakMap();
5
6
 
6
7
  function directCells(row) {
7
8
  return Array.from(row.children).filter(function(cell) {
@@ -100,11 +101,41 @@
100
101
  });
101
102
  }
102
103
 
104
+ function suppressResizeApply(table) {
105
+ if (!table) return;
106
+
107
+ var token = (suppressedResizeTables.get(table) || 0) + 1;
108
+ suppressedResizeTables.set(table, token);
109
+
110
+ window.setTimeout(function() {
111
+ if (suppressedResizeTables.get(table) === token) {
112
+ suppressedResizeTables.delete(table);
113
+ }
114
+ }, 250);
115
+ }
116
+
117
+ function resizeApplySuppressed(table) {
118
+ return suppressedResizeTables.has(table);
119
+ }
120
+
121
+ function refreshTable(table) {
122
+ if (!table) return false;
123
+
124
+ suppressResizeApply(table);
125
+ applyStickyColumns(table);
126
+
127
+ return true;
128
+ }
129
+
103
130
  function observeStickyColumns(table) {
104
131
  if (!window.ResizeObserver || resizeObservers.has(table)) return;
105
132
 
106
133
  var observer = new ResizeObserver(function() {
134
+ if (resizeApplySuppressed(table)) return;
135
+
107
136
  window.requestAnimationFrame(function() {
137
+ if (resizeApplySuppressed(table)) return;
138
+
108
139
  applyStickyColumns(table);
109
140
  });
110
141
  });
@@ -149,6 +180,10 @@
149
180
  document.addEventListener("turbo:load", initializeFromDocument);
150
181
  window.addEventListener("resize", initializeFromDocument);
151
182
 
183
+ window.YummyGuideAdministrateStickyLeftColumns = {
184
+ refreshTable: refreshTable
185
+ };
186
+
152
187
  if (window.MutationObserver) {
153
188
  var mutationObserver = new MutationObserver(function(mutations) {
154
189
  mutations.forEach(function(mutation) {
@@ -6,6 +6,7 @@
6
6
  var observedScrolls = new WeakMap();
7
7
  var observedFixedScrolls = new WeakMap();
8
8
  var scrollSyncStates = new WeakMap();
9
+ var suppressedResizeScrolls = new WeakMap();
9
10
 
10
11
  function directCells(row) {
11
12
  return Array.from(row.children).filter(function(cell) {
@@ -36,6 +37,11 @@
36
37
  return preciseNumber(Math.max(table.scrollWidth || 0, measuredWidth(table), widthsSum));
37
38
  }
38
39
 
40
+ function parsedPixelValue(value) {
41
+ var parsedValue = parseFloat(value || '0');
42
+ return Number.isNaN(parsedValue) ? 0 : preciseNumber(parsedValue);
43
+ }
44
+
39
45
  function fixedColumnsCount(table) {
40
46
  var rawCount = table.dataset.fixedColumnsCount || '0';
41
47
 
@@ -339,6 +345,19 @@
339
345
  });
340
346
  }
341
347
 
348
+ function applyColGroupColumnWidth(table, index, widthValue) {
349
+ if (!table) return;
350
+
351
+ var colgroup = table.querySelector('colgroup[data-fixed-header-colgroup]');
352
+ if (!colgroup) return;
353
+
354
+ while (colgroup.children.length <= index) {
355
+ colgroup.appendChild(document.createElement('col'));
356
+ }
357
+
358
+ colgroup.children[index].style.width = widthValue;
359
+ }
360
+
342
361
  function applyFixedHeaderCellWidths(fixedTable, widths) {
343
362
  var headerRow = fixedTable && fixedTable.querySelector('thead tr');
344
363
  if (!headerRow) return;
@@ -356,6 +375,56 @@
356
375
  });
357
376
  }
358
377
 
378
+ function applyFixedHeaderCellWidth(fixedTable, index, width) {
379
+ var headerRow = fixedTable && fixedTable.querySelector('thead tr');
380
+ if (!headerRow) return;
381
+
382
+ var cell = directCells(headerRow)[index];
383
+ if (!cell) return;
384
+
385
+ var widthValue = cssPixelValue(width);
386
+ cell.style.width = widthValue;
387
+ cell.style.minWidth = widthValue;
388
+ }
389
+
390
+ function columnIdentifier(cell) {
391
+ if (!cell) return '';
392
+
393
+ return cell.dataset.adminColumnResizerColumnId || cell.dataset.columnId || '';
394
+ }
395
+
396
+ function sourceColumnIndex(sourceTable, columnId) {
397
+ var sourceRow = sourceTable && sourceTable.querySelector('thead tr');
398
+ if (!sourceRow) return -1;
399
+
400
+ return directCells(sourceRow).findIndex(function(cell) {
401
+ return columnIdentifier(cell) === columnId;
402
+ });
403
+ }
404
+
405
+ function columnWidthsFromCurrentHeader(sourceTable, fixedTable, columnCount) {
406
+ var sourceRow = sourceTable && sourceTable.querySelector('thead tr');
407
+ var fixedRow = fixedTable && fixedTable.querySelector('thead tr');
408
+ var sourceCells = sourceRow ? directCells(sourceRow) : [];
409
+ var fixedCells = fixedRow ? directCells(fixedRow) : [];
410
+ var sourceColgroup = sourceTable && sourceTable.querySelector('colgroup[data-fixed-header-colgroup]');
411
+ var fixedColgroup = fixedTable && fixedTable.querySelector('colgroup[data-fixed-header-colgroup]');
412
+ var widths = [];
413
+
414
+ for (var index = 0; index < columnCount; index += 1) {
415
+ var fixedColWidth = fixedColgroup && fixedColgroup.children[index] && parsedPixelValue(fixedColgroup.children[index].style.width);
416
+ var sourceColWidth = sourceColgroup && sourceColgroup.children[index] && parsedPixelValue(sourceColgroup.children[index].style.width);
417
+
418
+ widths[index] = fixedColWidth ||
419
+ sourceColWidth ||
420
+ measuredWidth(fixedCells[index]) ||
421
+ measuredWidth(sourceCells[index]) ||
422
+ 0;
423
+ }
424
+
425
+ return widths;
426
+ }
427
+
359
428
  function mergeMeasuredCellWidths(widths, cells) {
360
429
  cells.forEach(function(cell, index) {
361
430
  widths[index] = Math.max(widths[index] || 0, measuredWidth(cell));
@@ -467,6 +536,24 @@
467
536
  });
468
537
  }
469
538
 
539
+ function applySourceColumnMinWidth(sourceTable, columnIndex, columnCount, width) {
540
+ if (!sourceTable) return;
541
+
542
+ var widthValue = cssPixelValue(width);
543
+ Array.from(sourceTable.querySelectorAll('thead tr, tbody tr, tfoot tr')).forEach(function(row) {
544
+ var cells = directCells(row);
545
+ if (cells.length !== columnCount || cells.some(function(cell) { return cell.colSpan > 1; })) return;
546
+
547
+ var cell = cells[columnIndex];
548
+ if (!cell) return;
549
+
550
+ cell.style.setProperty('--fixed-header-column-min-width', widthValue);
551
+ applyManagedCellWidth(cell, 'width', widthValue);
552
+ applyManagedCellWidth(cell, 'min-width', widthValue);
553
+ cell.setAttribute('data-fixed-header-column-min-width', 'true');
554
+ });
555
+ }
556
+
470
557
  function clearFixedHeaderState(slot, scroll, sourceTable) {
471
558
  if (slot) {
472
559
  slot.hidden = true;
@@ -534,6 +621,63 @@
534
621
  });
535
622
  }
536
623
 
624
+ function suppressResizeBuild(scroll) {
625
+ if (!scroll) return;
626
+
627
+ var token = (suppressedResizeScrolls.get(scroll) || 0) + 1;
628
+ suppressedResizeScrolls.set(scroll, token);
629
+
630
+ window.setTimeout(function() {
631
+ if (suppressedResizeScrolls.get(scroll) === token) {
632
+ suppressedResizeScrolls.delete(scroll);
633
+ }
634
+ }, 250);
635
+ }
636
+
637
+ function resizeBuildSuppressed(scroll) {
638
+ return suppressedResizeScrolls.has(scroll);
639
+ }
640
+
641
+ function refreshColumnWidth(options) {
642
+ var settings = options || {};
643
+ var sourceTable = settings.sourceTable;
644
+ var columnId = settings.columnId;
645
+ var width = parseFloat(settings.width);
646
+
647
+ if (!sourceTable || !columnId || Number.isNaN(width)) return false;
648
+
649
+ var scroll = sourceTable.closest(WRAPPER_SELECTOR);
650
+ if (!scroll) return false;
651
+
652
+ var slot = ensureFixedHeaderSlot(scroll);
653
+ var fixedScroll = slot && slot.querySelector('.table-fixed-header__scroll');
654
+ var fixedTable = fixedScroll && fixedScroll.querySelector('.table-fixed-header__table');
655
+ if (!slot || !fixedScroll || !fixedTable) return false;
656
+
657
+ var sourceRow = sourceTable.querySelector('thead tr');
658
+ var columnCount = sourceRow ? directCells(sourceRow).length : 0;
659
+ var columnIndex = sourceColumnIndex(sourceTable, columnId);
660
+ if (columnIndex < 0 || columnCount === 0) return false;
661
+
662
+ suppressResizeBuild(scroll);
663
+
664
+ var widthValue = cssPixelValue(width);
665
+ var widths = columnWidthsFromCurrentHeader(sourceTable, fixedTable, columnCount);
666
+ widths[columnIndex] = preciseNumber(width);
667
+
668
+ applySourceColumnMinWidth(sourceTable, columnIndex, columnCount, width);
669
+ applyColGroupColumnWidth(sourceTable, columnIndex, widthValue);
670
+ applyColGroupColumnWidth(fixedTable, columnIndex, widthValue);
671
+ applyFixedHeaderCellWidth(fixedTable, columnIndex, width);
672
+ applyFixedHeaderStickyColumns(sourceTable, fixedTable, widths);
673
+
674
+ fixedTable.style.width = cssPixelValue(measuredTableWidth(sourceTable, widths));
675
+ syncStickyPageHeader(scroll, slot);
676
+ syncFixedHeaderScroll(scroll, fixedScroll);
677
+
678
+ return true;
679
+ }
680
+
537
681
  function buildFixedTableHeader(slot, scroll, sourceTable) {
538
682
  if (!slot || !scroll || !sourceTable) return;
539
683
 
@@ -612,7 +756,11 @@
612
756
  var observer = resizeObservers.get(scroll);
613
757
  if (!observer) {
614
758
  observer = new ResizeObserver(function() {
759
+ if (resizeBuildSuppressed(scroll)) return;
760
+
615
761
  window.requestAnimationFrame(function() {
762
+ if (resizeBuildSuppressed(scroll)) return;
763
+
616
764
  initializeFixedHeaderForScroll(scroll);
617
765
  });
618
766
  });
@@ -721,6 +869,10 @@
721
869
  document.addEventListener('turbo:load', initializeFromDocument);
722
870
  window.addEventListener('resize', initializeFromDocument);
723
871
 
872
+ window.YummyGuideAdministrateStickyTableHeaders = {
873
+ refreshColumnWidth: refreshColumnWidth
874
+ };
875
+
724
876
  if (window.MutationObserver) {
725
877
  var mutationObserver = new MutationObserver(function(mutations) {
726
878
  mutations.forEach(function(mutation) {
@@ -1,3 +1,9 @@
1
+ table.admin-column-resizer__table:not(.table-fixed-header__table) {
2
+ table-layout: auto !important;
3
+ width: max-content !important;
4
+ min-width: 100% !important;
5
+ }
6
+
1
7
  .admin-column-resizer__header {
2
8
  position: relative;
3
9
  }
@@ -7,7 +13,7 @@
7
13
  top: 0;
8
14
  right: -5px;
9
15
  bottom: 0;
10
- z-index: 20;
16
+ z-index: 1;
11
17
  width: 12px;
12
18
  cursor: col-resize;
13
19
  touch-action: none;
@@ -42,6 +48,25 @@
42
48
  user-select: none !important;
43
49
  }
44
50
 
51
+ .admin-column-resizer--applying,
52
+ .admin-column-resizer--applying * {
53
+ cursor: wait !important;
54
+ user-select: none !important;
55
+ }
56
+
57
+ .admin-column-resizer__preview {
58
+ position: fixed;
59
+ z-index: 10000;
60
+ box-sizing: border-box;
61
+ pointer-events: none;
62
+ background: rgba(37, 99, 235, 0.12);
63
+ border-right: 2px solid rgba(37, 99, 235, 0.85);
64
+ border-left: 1px solid rgba(37, 99, 235, 0.35);
65
+ box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.12);
66
+ contain: layout paint style;
67
+ will-change: width;
68
+ }
69
+
45
70
  [data-fixed-table-header] .admin-column-resizer__handle {
46
71
  pointer-events: auto;
47
72
  }
@@ -52,13 +77,17 @@
52
77
 
53
78
  @media (pointer: coarse), screen and (max-width: 767px) {
54
79
  .admin-column-resizer__handle {
55
- right: -10px;
56
- width: 28px;
80
+ right: -14px;
81
+ width: 36px;
57
82
  }
58
83
 
59
84
  .admin-column-resizer__handle::after {
60
- right: 13px;
85
+ right: 17px;
61
86
  background: rgba(255, 255, 255, 0.75);
62
87
  opacity: 1;
63
88
  }
89
+
90
+ .admin-column-resizer__preview {
91
+ border-right-width: 3px;
92
+ }
64
93
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module YummyGuide
4
4
  module Administrate
5
- VERSION = "0.8.0"
5
+ VERSION = "0.8.1"
6
6
  end
7
7
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "column resizer assets" do
4
+ let(:javascript_source) do
5
+ File.read(File.expand_path("../../../app/assets/javascripts/yummy_guide_administrate/column_resizer.js", __dir__))
6
+ end
7
+
8
+ let(:stylesheet_source) do
9
+ File.read(File.expand_path("../../../app/assets/stylesheets/yummy_guide_administrate/_column_resizer.scss", __dir__))
10
+ end
11
+
12
+ let(:sticky_table_headers_source) do
13
+ File.read(File.expand_path("../../../app/assets/javascripts/yummy_guide_administrate/sticky_table_headers.js", __dir__))
14
+ end
15
+
16
+ let(:sticky_left_columns_source) do
17
+ File.read(File.expand_path("../../../app/assets/javascripts/yummy_guide_administrate/sticky_left_columns.js", __dir__))
18
+ end
19
+
20
+ # 未調整列は内容幅で表示され、調整済み列だけが幅固定されることを静的に確認する
21
+ it "scopes fixed width rules to adjusted columns" do
22
+ expect(javascript_source).to include("data-admin-column-resizer-adjusted-columns")
23
+ expect(javascript_source).to include("setAdjustedColumn(table, index, true)")
24
+ expect(javascript_source).to include("setAdjustedColumn(table, index, false)")
25
+ expect(javascript_source).to include("ADJUSTED_COLUMNS_ATTRIBUTE + '~=\"' + nthChild")
26
+ end
27
+
28
+ # 幅調整後の内容が常に折り返されるCSSが生成されることを確認する
29
+ it "generates wrapping rules for adjusted columns" do
30
+ expect(javascript_source).to include("white-space: normal !important")
31
+ expect(javascript_source).to include("overflow-wrap: anywhere !important")
32
+ expect(javascript_source).to include("word-break: break-word !important")
33
+ end
34
+
35
+ # デフォルト状態の列幅が内容の最大幅を基準に決まることを確認する
36
+ it "uses max-content table sizing by default" do
37
+ expect(stylesheet_source).to include("table-layout: auto !important")
38
+ expect(stylesheet_source).to include("width: max-content !important")
39
+ expect(stylesheet_source).to include("min-width: 100% !important")
40
+ end
41
+
42
+ # ドラッグ中に固定ヘッダー同期を繰り返さず、調整完了後だけ同期することを静的に確認する
43
+ it "synchronizes fixed headers after drag completion instead of during drag" do
44
+ expect(javascript_source).not_to include("scheduleStickyRefresh(false)")
45
+ expect(javascript_source).not_to include("refreshDuringDrag")
46
+ expect(javascript_source).not_to include("columnNeedsDragRefresh")
47
+ expect(javascript_source).not_to include("STICKY_REFRESH_INTERVAL")
48
+ expect(javascript_source).to include("refreshStickyHeaderColumn(pendingWidth, function()")
49
+ expect(javascript_source).to include("scheduleStickyRefresh(callback)")
50
+ end
51
+
52
+ # ドラッグ中は実テーブルを再レイアウトせず、プレビューだけを更新することを静的に確認する
53
+ it "updates only the lightweight preview while dragging" do
54
+ expect(javascript_source).to include("createDragPreview(sourceTable, previewHeader, startWidth)")
55
+ expect(javascript_source).to include("updateDragPreview(dragState.preview, dragState.currentWidth)")
56
+ expect(javascript_source).to include("schedulePendingWidthApply(pendingWidth)")
57
+ expect(javascript_source).to include("applyColumnWidth(pendingWidth.columnId, pendingWidth.width, pendingWidth.storageKey)")
58
+ expect(javascript_source).to include("sourceTable: dragState.sourceTable")
59
+ expect(javascript_source).not_to include("applyColumnWidth(dragState.columnId, dragState.currentWidth")
60
+ end
61
+
62
+ # ドラッグ中の調整後幅を半透明カラムで表示するCSSがあることを確認する
63
+ it "defines a translucent column preview" do
64
+ expect(stylesheet_source).to include(".admin-column-resizer__preview")
65
+ expect(stylesheet_source).to include("pointer-events: none")
66
+ expect(stylesheet_source).to include("will-change: width")
67
+ end
68
+
69
+ # 幅調整中のpxラベルが表示されないことを静的に確認する
70
+ it "does not render a pixel width label in the preview" do
71
+ expect(javascript_source).not_to include("PREVIEW_LABEL_CLASS")
72
+ expect(javascript_source).not_to include("admin-column-resizer__preview-label")
73
+ expect(javascript_source).not_to include("Math.round(width) + 'px'")
74
+ expect(stylesheet_source).not_to include(".admin-column-resizer__preview-label")
75
+ end
76
+
77
+ # 固定列ヘッダーより幅調整ハンドルを背面にすることを静的に確認する
78
+ it "keeps resize handles behind sticky column headers" do
79
+ expect(stylesheet_source).to include("z-index: 1")
80
+ expect(stylesheet_source).not_to include("z-index: 20")
81
+ end
82
+
83
+ # 幅の適用中だけ待機カーソルを表示することを静的に確認する
84
+ it "shows a wait cursor while applying the final width" do
85
+ expect(javascript_source).to include("APPLYING_BODY_CLASS = 'admin-column-resizer--applying'")
86
+ expect(javascript_source).to include("document.body.classList.add(APPLYING_BODY_CLASS)")
87
+ expect(javascript_source).to include("document.body.classList.remove(APPLYING_BODY_CLASS)")
88
+ expect(stylesheet_source).to include(".admin-column-resizer--applying")
89
+ expect(stylesheet_source).to include("cursor: wait !important")
90
+ end
91
+
92
+ # モバイルのtouch/pen操作でハンドル外へ移動しても調整を継続できることを静的に確認する
93
+ it "supports touch and pen pointer dragging on mobile" do
94
+ expect(javascript_source).to include("event.pointerType !== 'mouse' || event.button === 0")
95
+ expect(javascript_source).to include("event.isPrimary === false")
96
+ expect(javascript_source).to include("handle.setPointerCapture(event.pointerId)")
97
+ expect(javascript_source).to include("dragState.handle.releasePointerCapture(dragState.pointerId)")
98
+ expect(stylesheet_source).to include("right: -14px")
99
+ expect(stylesheet_source).to include("width: 36px")
100
+ end
101
+
102
+ # 幅適用後の固定ヘッダー追従が全体rebuildではなく対象カラム同期を優先することを静的に確認する
103
+ it "uses targeted fixed header synchronization after applying a width" do
104
+ expect(javascript_source).to include("window.YummyGuideAdministrateStickyTableHeaders")
105
+ expect(javascript_source).to include("api.refreshColumnWidth({")
106
+ expect(sticky_table_headers_source).to include("window.YummyGuideAdministrateStickyTableHeaders = {")
107
+ expect(sticky_table_headers_source).to include("refreshColumnWidth: refreshColumnWidth")
108
+ expect(sticky_table_headers_source).to include("function refreshColumnWidth(options)")
109
+ expect(sticky_table_headers_source).to include("applySourceColumnMinWidth(sourceTable, columnIndex, columnCount, width)")
110
+ expect(sticky_table_headers_source).to include("applyFixedHeaderCellWidth(fixedTable, columnIndex, width)")
111
+ expect(sticky_table_headers_source).to include("suppressResizeBuild(scroll)")
112
+ end
113
+
114
+ # 幅適用後の固定左列追従もResizeObserverの重複再計算に頼らないことを静的に確認する
115
+ it "refreshes sticky left columns directly after applying a width" do
116
+ expect(javascript_source).to include("window.YummyGuideAdministrateStickyLeftColumns")
117
+ expect(javascript_source).to include("api.refreshTable(sourceTable)")
118
+ expect(javascript_source).to include("refreshStickyLeftColumns(pendingWidth.sourceTable)")
119
+ expect(sticky_left_columns_source).to include("window.YummyGuideAdministrateStickyLeftColumns = {")
120
+ expect(sticky_left_columns_source).to include("refreshTable: refreshTable")
121
+ expect(sticky_left_columns_source).to include("function refreshTable(table)")
122
+ expect(sticky_left_columns_source).to include("suppressResizeApply(table)")
123
+ end
124
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yummy-guide-generic-administrate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - akatsuki-kk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-04 00:00:00.000000000 Z
11
+ date: 2026-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: administrate
@@ -142,6 +142,7 @@ files:
142
142
  - spec/yummy/guide/generic/administrate_spec.rb
143
143
  - spec/yummy_guide/administrate/application_dashboard_spec.rb
144
144
  - spec/yummy_guide/administrate/collection_helper_spec.rb
145
+ - spec/yummy_guide/administrate/column_resizer_asset_spec.rb
145
146
  - spec/yummy_guide/administrate/datetime_filter_parameters_spec.rb
146
147
  - spec/yummy_guide/administrate/datetime_input_helper_spec.rb
147
148
  - spec/yummy_guide/administrate/fields/json_pretty_field_spec.rb