yummy-guide-generic-administrate 0.8.0 → 0.8.2

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: 55b8c25b868ffa217ed45c64c2c0b53aa46bdb9d60094c022d9979cdd7a4e013
4
+ data.tar.gz: 4889ad38b64d46b34ce69f7e5cb4819c53d71a9c13636096dc94a5721e94fa87
5
5
  SHA512:
6
- metadata.gz: ecc7d99ccc8f23f5b4d0dd9288a9d6f870f95dc9c2d19256865828fa9a3d1f10d322fd37d627d2e85d648e13ab1fb128fb2d0430035451147fa2f4b72739bbb4
7
- data.tar.gz: 05b2097517ad48edd1e8f54230bd3f2d1ea5e591dccc733d0397e1ec89d7d3b855920066296b4119c6c4b7b46572413ca07730dae2663e584cf403d1bc0f6cc9
6
+ metadata.gz: e85e0d5ef1a103b6f72b9544dabc6ec02118bd4c845c2dabffaa47e4b4d66de21cf227277e196ee70c4285aa76721766d9c8a465665c233e7e22a68ba32d9b2a
7
+ data.tar.gz: 508817a41590cac77f1b1f3ecfbb676784ee6e5655dd1336f2e0216c5700100c9c66f6a36c56830c4f8ee611a0ae117cf2a6ee220b82eb5b77913af54d50e708
@@ -4,21 +4,25 @@
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;
19
+ var pendingWidthApply = null;
16
20
  var generatedRuleCount = 0;
17
21
  var initializedHandles = new WeakSet();
18
22
  var tableStates = new WeakMap();
19
23
  var stickyRefreshFrame = null;
20
- var stickyRefreshTimer = null;
21
- var lastStickyRefreshAt = 0;
24
+ var stickyRefreshCallbacks = [];
25
+ var applyingWidth = false;
22
26
 
23
27
  function storageScopeForTable(table) {
24
28
  var sourceTable = matchingSourceTable(table);
@@ -74,6 +78,10 @@
74
78
  return preciseNumber(rectWidth || element.offsetWidth || 0);
75
79
  }
76
80
 
81
+ function viewportHeight() {
82
+ return window.innerHeight || document.documentElement.clientHeight || 0;
83
+ }
84
+
77
85
  function normalizedIdentifier(value) {
78
86
  return (value || '').toString().trim().toLowerCase()
79
87
  .replace(/[^a-z0-9]+/g, '_')
@@ -169,13 +177,22 @@
169
177
  function columnRule(index) {
170
178
  var nthChild = index + 1;
171
179
  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(', ');
180
+ var adjustedTableSelector = '.' + TABLE_CLASS + '[' + ADJUSTED_COLUMNS_ATTRIBUTE + '~="' + nthChild + '"]';
181
+ var selectors = [
182
+ adjustedTableSelector + ' > thead > tr > :nth-child(' + nthChild + ')',
183
+ adjustedTableSelector + ' > tbody > tr > :nth-child(' + nthChild + ')',
184
+ adjustedTableSelector + ' > tfoot > tr > :nth-child(' + nthChild + ')'
185
+ ];
186
+ var selector = selectors.join(', ');
187
+
188
+ var contentSelector = selectors.concat(selectors.map(function(cellSelector) {
189
+ return cellSelector + ' *';
190
+ })).join(', ');
177
191
 
178
- return selector + ' { box-sizing: border-box !important; width: var(' + variableName + ') !important; min-width: var(' + variableName + ') !important; max-width: var(' + variableName + ') !important; }';
192
+ return [
193
+ selector + ' { box-sizing: border-box !important; width: var(' + variableName + ') !important; min-width: var(' + variableName + ') !important; max-width: var(' + variableName + ') !important; }',
194
+ contentSelector + ' { white-space: normal !important; overflow-wrap: anywhere !important; word-break: break-word !important; }'
195
+ ].join('\n');
179
196
  }
180
197
 
181
198
  function ensureStyleElement() {
@@ -315,6 +332,33 @@
315
332
  colgroup.children[index].style.width = widthValue;
316
333
  }
317
334
 
335
+ function adjustedColumnIndexes(table) {
336
+ return (table.getAttribute(ADJUSTED_COLUMNS_ATTRIBUTE) || '')
337
+ .split(/\s+/)
338
+ .filter(Boolean);
339
+ }
340
+
341
+ function setAdjustedColumn(table, index, adjusted) {
342
+ var indexToken = (index + 1).toString();
343
+ var indexes = adjustedColumnIndexes(table).filter(function(candidate, candidateIndex, candidates) {
344
+ return candidate !== indexToken && candidates.indexOf(candidate) === candidateIndex;
345
+ });
346
+
347
+ if (adjusted) {
348
+ indexes.push(indexToken);
349
+ indexes.sort(function(left, right) {
350
+ return parseInt(left, 10) - parseInt(right, 10);
351
+ });
352
+ }
353
+
354
+ if (indexes.length === 0) {
355
+ table.removeAttribute(ADJUSTED_COLUMNS_ATTRIBUTE);
356
+ return;
357
+ }
358
+
359
+ table.setAttribute(ADJUSTED_COLUMNS_ATTRIBUTE, indexes.join(' '));
360
+ }
361
+
318
362
  function clearColgroupWidth(table, columnCount, index) {
319
363
  var colgroup = ensureManagedColgroup(table, columnCount);
320
364
  if (!colgroup || !colgroup.children[index]) return;
@@ -329,6 +373,7 @@
329
373
 
330
374
  var widthValue = cssPixelValue(width);
331
375
  table.style.setProperty(columnWidthVariable(index), widthValue);
376
+ setAdjustedColumn(table, index, true);
332
377
  applyColgroupWidth(table, state.columnCount, index, widthValue);
333
378
  }
334
379
 
@@ -338,6 +383,7 @@
338
383
  if (!state || index === undefined) return;
339
384
 
340
385
  table.style.removeProperty(columnWidthVariable(index));
386
+ setAdjustedColumn(table, index, false);
341
387
  clearColgroupWidth(table, state.columnCount, index);
342
388
  }
343
389
 
@@ -381,26 +427,23 @@
381
427
 
382
428
  function dispatchStickyRefresh() {
383
429
  stickyRefreshFrame = null;
384
- lastStickyRefreshAt = Date.now();
385
430
  window.dispatchEvent(new Event('resize'));
386
- }
387
431
 
388
- function scheduleStickyRefresh(immediate) {
389
- if (stickyRefreshFrame || stickyRefreshTimer) return;
432
+ var callbacks = stickyRefreshCallbacks;
433
+ stickyRefreshCallbacks = [];
434
+ callbacks.forEach(function(callback) {
435
+ callback();
436
+ });
437
+ }
390
438
 
391
- var elapsed = Date.now() - lastStickyRefreshAt;
392
- var delay = immediate ? 0 : Math.max(0, STICKY_REFRESH_INTERVAL - elapsed);
439
+ function scheduleStickyRefresh(callback) {
440
+ if (callback) {
441
+ stickyRefreshCallbacks.push(callback);
442
+ }
393
443
 
394
- stickyRefreshTimer = window.setTimeout(function() {
395
- stickyRefreshTimer = null;
396
- stickyRefreshFrame = window.requestAnimationFrame(dispatchStickyRefresh);
397
- }, delay);
398
- }
444
+ if (stickyRefreshFrame) return;
399
445
 
400
- function columnNeedsDragRefresh(header) {
401
- return header.classList.contains('sticky') ||
402
- header.classList.contains('sticky-left') ||
403
- header.classList.contains('actions-column');
446
+ stickyRefreshFrame = window.requestAnimationFrame(dispatchStickyRefresh);
404
447
  }
405
448
 
406
449
  function sourceTableForHandle(handle) {
@@ -419,34 +462,269 @@
419
462
  }) || matchingSourceTable(table) || null;
420
463
  }
421
464
 
422
- function flushDragWidth() {
465
+ function previewBoundsForTable(table, header) {
466
+ var headerRect = header.getBoundingClientRect();
467
+ var tableRect = table.getBoundingClientRect();
468
+ var scrollContainer = table.closest('.sticky-table-scroll, .scroll-table, .home-table__wrapper');
469
+ var scrollRect = scrollContainer ? scrollContainer.getBoundingClientRect() : tableRect;
470
+ var headerTable = header.closest(TABLE_SELECTOR);
471
+ var fromFixedHeader = headerTable && headerTable.classList.contains(FIXED_HEADER_TABLE_CLASS);
472
+ var viewportBottom = viewportHeight();
473
+ var top = fromFixedHeader ? headerRect.top : Math.max(tableRect.top, scrollRect.top);
474
+ var bottom = Math.min(viewportBottom, Math.max(tableRect.bottom, scrollRect.bottom, headerRect.bottom));
475
+
476
+ top = Math.max(0, Math.min(top, viewportBottom));
477
+ if (bottom <= top) {
478
+ bottom = Math.min(viewportBottom, top + Math.max(headerRect.height, 32));
479
+ }
480
+
481
+ return {
482
+ left: headerRect.left,
483
+ top: top,
484
+ height: Math.max(32, bottom - top)
485
+ };
486
+ }
487
+
488
+ function updateDragPreview(preview, width) {
489
+ if (!preview) return;
490
+
491
+ preview.element.style.width = cssPixelValue(width);
492
+ }
493
+
494
+ function createDragPreview(table, header, width) {
495
+ var bounds = previewBoundsForTable(table, header);
496
+ var element = document.createElement('div');
497
+
498
+ element.className = PREVIEW_CLASS;
499
+ element.setAttribute('aria-hidden', 'true');
500
+ element.style.left = cssPixelValue(bounds.left);
501
+ element.style.top = cssPixelValue(bounds.top);
502
+ element.style.height = cssPixelValue(bounds.height);
503
+
504
+ document.body.appendChild(element);
505
+
506
+ var preview = {
507
+ element: element
508
+ };
509
+ updateDragPreview(preview, width);
510
+
511
+ return preview;
512
+ }
513
+
514
+ function removePreview(preview) {
515
+ if (!preview) return;
516
+
517
+ preview.element.remove();
518
+ }
519
+
520
+ function removeDragPreview() {
521
+ if (!dragState || !dragState.preview) return;
522
+
523
+ removePreview(dragState.preview);
524
+ dragState.preview = null;
525
+ }
526
+
527
+ function flushDragPreview() {
423
528
  if (!dragState) return;
424
529
 
425
- if (dragApplyFrame) {
426
- window.cancelAnimationFrame(dragApplyFrame);
427
- dragApplyFrame = null;
530
+ if (dragPreviewFrame) {
531
+ window.cancelAnimationFrame(dragPreviewFrame);
532
+ dragPreviewFrame = null;
533
+ }
534
+
535
+ updateDragPreview(dragState.preview, dragState.currentWidth || dragState.startWidth);
536
+ }
537
+
538
+ function releaseDragPointerCapture() {
539
+ if (!dragState || !dragState.handle || dragState.pointerId === null || typeof dragState.pointerId === 'undefined') return;
540
+ if (!dragState.handle.releasePointerCapture) return;
541
+
542
+ try {
543
+ dragState.handle.releasePointerCapture(dragState.pointerId);
544
+ } catch (_error) {
545
+ // The pointer may already be released by the browser after cancellation.
546
+ }
547
+ }
548
+
549
+ function captureDragPointer(handle, event) {
550
+ if (!handle.setPointerCapture || event.pointerId === null || typeof event.pointerId === 'undefined') return;
551
+
552
+ try {
553
+ handle.setPointerCapture(event.pointerId);
554
+ } catch (_error) {
555
+ // Pointer capture is an enhancement for touch/pen dragging; document listeners remain as fallback.
556
+ }
557
+ }
558
+
559
+ function eventMatchesDragPointer(event) {
560
+ return !event ||
561
+ dragState.pointerId === null ||
562
+ typeof dragState.pointerId === 'undefined' ||
563
+ event.pointerId === dragState.pointerId;
564
+ }
565
+
566
+ function pointerCanStartDrag(event) {
567
+ if (event.isPrimary === false) return false;
568
+
569
+ return event.pointerType !== 'mouse' || event.button === 0;
570
+ }
571
+
572
+ function stopDragging(removePreview) {
573
+ if (!dragState) return null;
574
+
575
+ if (dragPreviewFrame) {
576
+ window.cancelAnimationFrame(dragPreviewFrame);
577
+ dragPreviewFrame = null;
578
+ }
579
+
580
+ if (removePreview) {
581
+ removeDragPreview();
428
582
  }
429
583
 
430
- applyColumnWidth(dragState.columnId, dragState.currentWidth || dragState.startWidth, dragState.storageKey);
584
+ releaseDragPointerCapture();
585
+
586
+ var completedDrag = dragState;
587
+ dragState = null;
588
+ document.body.classList.remove(DRAGGING_BODY_CLASS);
589
+ document.removeEventListener('pointermove', handleDragMove);
590
+ document.removeEventListener('pointerup', finishDrag);
591
+ document.removeEventListener('pointercancel', finishDrag);
592
+
593
+ return completedDrag;
431
594
  }
432
595
 
433
596
  function scheduleDragWidth(width) {
434
597
  dragState.currentWidth = width;
435
598
  dragState.moved = dragState.moved || Math.abs(width - dragState.startWidth) > 2;
436
599
 
437
- if (dragApplyFrame) return;
600
+ if (dragPreviewFrame) return;
438
601
 
439
- dragApplyFrame = window.requestAnimationFrame(function() {
440
- dragApplyFrame = null;
602
+ dragPreviewFrame = window.requestAnimationFrame(function() {
603
+ dragPreviewFrame = null;
441
604
  if (!dragState) return;
442
605
 
443
- applyColumnWidth(dragState.columnId, dragState.currentWidth, dragState.storageKey);
444
- if (dragState.refreshDuringDrag) scheduleStickyRefresh(false);
606
+ updateDragPreview(dragState.preview, dragState.currentWidth);
607
+ });
608
+ }
609
+
610
+ function refreshStickyHeaderColumn(pendingWidth, callback) {
611
+ var api = window.YummyGuideAdministrateStickyTableHeaders;
612
+
613
+ if (api && typeof api.refreshColumnWidth === 'function') {
614
+ var refreshed = api.refreshColumnWidth({
615
+ sourceTable: pendingWidth.sourceTable,
616
+ columnId: pendingWidth.columnId,
617
+ width: pendingWidth.width
618
+ });
619
+
620
+ if (refreshed) {
621
+ window.requestAnimationFrame(callback);
622
+ return;
623
+ }
624
+ }
625
+
626
+ scheduleStickyRefresh(callback);
627
+ }
628
+
629
+ function refreshStickyHeaderTable(sourceTable, callback) {
630
+ var api = window.YummyGuideAdministrateStickyTableHeaders;
631
+
632
+ if (api && typeof api.refreshTable === 'function') {
633
+ var refreshed = api.refreshTable(sourceTable, callback);
634
+
635
+ if (refreshed) return;
636
+ }
637
+
638
+ scheduleStickyRefresh(callback);
639
+ }
640
+
641
+ function refreshStickyLeftColumns(sourceTable) {
642
+ var api = window.YummyGuideAdministrateStickyLeftColumns;
643
+
644
+ if (api && typeof api.refreshTable === 'function') {
645
+ api.refreshTable(sourceTable);
646
+ }
647
+ }
648
+
649
+ function refreshStickyLeftColumnsForWidth(pendingWidth) {
650
+ var api = window.YummyGuideAdministrateStickyLeftColumns;
651
+
652
+ if (!api) return;
653
+
654
+ if (typeof api.refreshColumnWidth === 'function') {
655
+ var refreshed = api.refreshColumnWidth({
656
+ sourceTable: pendingWidth.sourceTable,
657
+ columnId: pendingWidth.columnId,
658
+ width: pendingWidth.width
659
+ });
660
+
661
+ if (refreshed) return;
662
+ }
663
+
664
+ refreshStickyLeftColumns(pendingWidth.sourceTable);
665
+ }
666
+
667
+ function startApplyingWidth() {
668
+ applyingWidth = true;
669
+ document.body.classList.add(APPLYING_BODY_CLASS);
670
+ }
671
+
672
+ function stopApplyingState() {
673
+ applyingWidth = false;
674
+ document.body.classList.remove(APPLYING_BODY_CLASS);
675
+ }
676
+
677
+ function stopApplyingWidth(preview) {
678
+ removePreview(preview);
679
+ pendingWidthApply = null;
680
+ stopApplyingState();
681
+ }
682
+
683
+ function cancelPendingWidthApply() {
684
+ if (widthApplyFrame) {
685
+ window.cancelAnimationFrame(widthApplyFrame);
686
+ widthApplyFrame = null;
687
+ }
688
+
689
+ if (pendingWidthApply) {
690
+ removePreview(pendingWidthApply.preview);
691
+ pendingWidthApply = null;
692
+ }
693
+
694
+ stopApplyingState();
695
+ }
696
+
697
+ function applyPendingWidth(pendingWidth) {
698
+ try {
699
+ var widths = safeReadWidths(pendingWidth.storageKey);
700
+ applyColumnWidth(pendingWidth.columnId, pendingWidth.width, pendingWidth.storageKey);
701
+ widths[pendingWidth.columnId] = preciseNumber(pendingWidth.width);
702
+ safeWriteWidths(pendingWidth.storageKey, widths);
703
+ refreshStickyLeftColumnsForWidth(pendingWidth);
704
+ refreshStickyHeaderColumn(pendingWidth, function() {
705
+ stopApplyingWidth(pendingWidth.preview);
706
+ });
707
+ } catch (error) {
708
+ stopApplyingWidth(pendingWidth.preview);
709
+ throw error;
710
+ }
711
+ }
712
+
713
+ function schedulePendingWidthApply(pendingWidth) {
714
+ pendingWidthApply = pendingWidth;
715
+ startApplyingWidth();
716
+
717
+ widthApplyFrame = window.requestAnimationFrame(function() {
718
+ widthApplyFrame = window.requestAnimationFrame(function() {
719
+ widthApplyFrame = null;
720
+ applyPendingWidth(pendingWidth);
721
+ });
445
722
  });
446
723
  }
447
724
 
448
725
  function startDrag(event) {
449
- if (event.button !== 0) return;
726
+ if (!pointerCanStartDrag(event)) return;
727
+ if (applyingWidth || widthApplyFrame) return;
450
728
 
451
729
  var handle = event.currentTarget;
452
730
  var header = handle.closest('th');
@@ -459,8 +737,11 @@
459
737
  if (!sourceTable) return;
460
738
 
461
739
  var sourceHeader = columnHeader(sourceTable, columnId) || header;
462
- var startWidth = measuredWidth(sourceHeader) || measuredWidth(header);
740
+ var sourceHeaderWidth = measuredWidth(sourceHeader);
741
+ var handleHeaderWidth = measuredWidth(header);
742
+ var startWidth = sourceHeaderWidth || handleHeaderWidth;
463
743
  if (!startWidth) return;
744
+ var previewHeader = sourceHeaderWidth ? sourceHeader : header;
464
745
 
465
746
  event.preventDefault();
466
747
  event.stopPropagation();
@@ -471,10 +752,14 @@
471
752
  startX: event.clientX,
472
753
  startWidth: startWidth,
473
754
  currentWidth: startWidth,
755
+ pointerId: event.pointerId,
756
+ handle: handle,
757
+ sourceTable: sourceTable,
474
758
  moved: false,
475
- refreshDuringDrag: columnNeedsDragRefresh(sourceHeader)
759
+ preview: createDragPreview(sourceTable, previewHeader, startWidth)
476
760
  };
477
761
 
762
+ captureDragPointer(handle, event);
478
763
  document.body.classList.add(DRAGGING_BODY_CLASS);
479
764
  document.addEventListener('pointermove', handleDragMove);
480
765
  document.addEventListener('pointerup', finishDrag);
@@ -483,6 +768,7 @@
483
768
 
484
769
  function handleDragMove(event) {
485
770
  if (!dragState) return;
771
+ if (!eventMatchesDragPointer(event)) return;
486
772
 
487
773
  event.preventDefault();
488
774
 
@@ -491,24 +777,28 @@
491
777
 
492
778
  function finishDrag(event) {
493
779
  if (!dragState) return;
780
+ if (!eventMatchesDragPointer(event)) return;
494
781
 
495
782
  if (event) {
496
783
  event.preventDefault();
497
784
  }
498
785
 
499
- flushDragWidth();
786
+ flushDragPreview();
787
+ var pointerCancelled = event && event.type === 'pointercancel';
788
+ var shouldApplyWidth = dragState.moved && !pointerCancelled;
500
789
 
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);
790
+ var pendingWidth = {
791
+ columnId: dragState.columnId,
792
+ storageKey: dragState.storageKey,
793
+ sourceTable: dragState.sourceTable,
794
+ width: Math.max(MIN_WIDTH, dragState.currentWidth || dragState.startWidth),
795
+ preview: dragState.preview
796
+ };
505
797
 
506
- dragState = null;
507
- document.body.classList.remove(DRAGGING_BODY_CLASS);
508
- document.removeEventListener('pointermove', handleDragMove);
509
- document.removeEventListener('pointerup', finishDrag);
510
- document.removeEventListener('pointercancel', finishDrag);
511
- scheduleStickyRefresh(true);
798
+ stopDragging(!shouldApplyWidth);
799
+ if (!shouldApplyWidth) return;
800
+
801
+ schedulePendingWidthApply(pendingWidth);
512
802
  }
513
803
 
514
804
  function resetColumn(event) {
@@ -521,6 +811,8 @@
521
811
 
522
812
  event.preventDefault();
523
813
  event.stopPropagation();
814
+ cancelPendingWidthApply();
815
+ stopDragging(true);
524
816
 
525
817
  var sourceTable = sourceTableForHandle(handle);
526
818
  var key = storageKeyForTable(sourceTable || handle.closest(TABLE_SELECTOR));
@@ -528,7 +820,17 @@
528
820
  delete widths[columnId];
529
821
  safeWriteWidths(key, widths);
530
822
  clearColumnWidth(columnId, key);
531
- scheduleStickyRefresh(true);
823
+ startApplyingWidth();
824
+ if (sourceTable) {
825
+ refreshStickyHeaderTable(sourceTable, function() {
826
+ refreshStickyLeftColumns(sourceTable);
827
+ stopApplyingWidth(null);
828
+ });
829
+ } else {
830
+ scheduleStickyRefresh(function() {
831
+ stopApplyingWidth(null);
832
+ });
833
+ }
532
834
  }
533
835
 
534
836
  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) {
@@ -24,6 +25,37 @@
24
25
  return preciseNumber(value) + "px";
25
26
  }
26
27
 
28
+ function headerColumnId(header) {
29
+ return header.dataset.adminColumnResizerColumnId || header.dataset.columnId || "";
30
+ }
31
+
32
+ function columnIndex(headerCells, columnId) {
33
+ if (!columnId) return -1;
34
+
35
+ return headerCells.findIndex(function(header) {
36
+ return headerColumnId(header) === columnId;
37
+ });
38
+ }
39
+
40
+ function widthOverride(headerCells, options) {
41
+ var settings = options || {};
42
+ var width = parseFloat(settings.width);
43
+ var index = columnIndex(headerCells, settings.columnId);
44
+
45
+ if (index < 0 || Number.isNaN(width)) return null;
46
+
47
+ return {
48
+ index: index,
49
+ width: preciseNumber(width)
50
+ };
51
+ }
52
+
53
+ function applyWidthOverride(widths, override) {
54
+ if (!override || override.index >= widths.length) return;
55
+
56
+ widths[override.index] = override.width;
57
+ }
58
+
27
59
  function resetStickyColumns(table) {
28
60
  table.querySelectorAll(".sticky-left").forEach(function(cell) {
29
61
  cell.classList.remove("sticky-left", "sticky-left--last");
@@ -46,15 +78,18 @@
46
78
  return window.matchMedia && window.matchMedia(MOBILE_MEDIA_QUERY).matches;
47
79
  }
48
80
 
49
- function stickyOffsets(table, count) {
81
+ function stickyOffsets(table, count, options) {
50
82
  var headerRow = table.querySelector("thead tr");
51
83
  if (!headerRow) return [];
52
84
 
53
- var widths = directCells(headerRow).slice(0, count).map(function(cell) {
85
+ var headerCells = directCells(headerRow);
86
+ var override = widthOverride(headerCells, options);
87
+ var widths = headerCells.slice(0, count).map(function(cell) {
54
88
  return measuredWidth(cell);
55
89
  });
90
+ var hasMeasuredWidth = widths.some(Boolean);
56
91
 
57
- if (!widths.some(Boolean)) {
92
+ if (!hasMeasuredWidth) {
58
93
  Array.from(table.querySelectorAll("tbody tr, tfoot tr")).some(function(row) {
59
94
  var cells = directCells(row);
60
95
  if (cells.length < count || cells.some(function(cell) { return cell.colSpan > 1; })) return false;
@@ -66,6 +101,8 @@
66
101
  });
67
102
  }
68
103
 
104
+ applyWidthOverride(widths, override);
105
+
69
106
  var offsets = [];
70
107
  var currentLeft = 0;
71
108
 
@@ -77,13 +114,13 @@
77
114
  return offsets;
78
115
  }
79
116
 
80
- function applyStickyColumns(table) {
117
+ function applyStickyColumns(table, options) {
81
118
  resetStickyColumns(table);
82
119
 
83
120
  var count = fixedColumnsCount(table);
84
121
  if (count === 0) return;
85
122
 
86
- var offsets = stickyOffsets(table, count);
123
+ var offsets = stickyOffsets(table, count, options);
87
124
  if (offsets.length === 0) return;
88
125
 
89
126
  table.querySelectorAll("thead tr, tbody tr, tfoot tr").forEach(function(row) {
@@ -100,11 +137,56 @@
100
137
  });
101
138
  }
102
139
 
140
+ function suppressResizeApply(table) {
141
+ if (!table) return;
142
+
143
+ var token = (suppressedResizeTables.get(table) || 0) + 1;
144
+ suppressedResizeTables.set(table, token);
145
+
146
+ window.setTimeout(function() {
147
+ if (suppressedResizeTables.get(table) === token) {
148
+ suppressedResizeTables.delete(table);
149
+ }
150
+ }, 250);
151
+ }
152
+
153
+ function resizeApplySuppressed(table) {
154
+ return suppressedResizeTables.has(table);
155
+ }
156
+
157
+ function refreshTable(table) {
158
+ if (!table) return false;
159
+
160
+ suppressResizeApply(table);
161
+ applyStickyColumns(table);
162
+
163
+ return true;
164
+ }
165
+
166
+ function refreshColumnWidth(options) {
167
+ var settings = options || {};
168
+ var table = settings.sourceTable;
169
+
170
+ if (!table) return false;
171
+
172
+ suppressResizeApply(table);
173
+ applyStickyColumns(table, {
174
+ columnId: settings.columnId,
175
+ width: settings.width
176
+ });
177
+
178
+ return true;
179
+ }
180
+
103
181
  function observeStickyColumns(table) {
104
182
  if (!window.ResizeObserver || resizeObservers.has(table)) return;
105
183
 
106
184
  var observer = new ResizeObserver(function() {
185
+ if (resizeApplySuppressed(table)) return;
186
+
107
187
  window.requestAnimationFrame(function() {
188
+ if (resizeApplySuppressed(table)) return;
189
+
108
190
  applyStickyColumns(table);
109
191
  });
110
192
  });
@@ -149,6 +231,11 @@
149
231
  document.addEventListener("turbo:load", initializeFromDocument);
150
232
  window.addEventListener("resize", initializeFromDocument);
151
233
 
234
+ window.YummyGuideAdministrateStickyLeftColumns = {
235
+ refreshColumnWidth: refreshColumnWidth,
236
+ refreshTable: refreshTable
237
+ };
238
+
152
239
  if (window.MutationObserver) {
153
240
  var mutationObserver = new MutationObserver(function(mutations) {
154
241
  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,79 @@
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
+
681
+ function refreshTable(sourceTable, callback) {
682
+ if (!sourceTable) return false;
683
+
684
+ var scroll = sourceTable.closest(WRAPPER_SELECTOR);
685
+ if (!scroll) return false;
686
+
687
+ suppressResizeBuild(scroll);
688
+ initializeFixedHeaderForScroll(scroll);
689
+
690
+ if (callback) {
691
+ window.requestAnimationFrame(callback);
692
+ }
693
+
694
+ return true;
695
+ }
696
+
537
697
  function buildFixedTableHeader(slot, scroll, sourceTable) {
538
698
  if (!slot || !scroll || !sourceTable) return;
539
699
 
@@ -612,7 +772,11 @@
612
772
  var observer = resizeObservers.get(scroll);
613
773
  if (!observer) {
614
774
  observer = new ResizeObserver(function() {
775
+ if (resizeBuildSuppressed(scroll)) return;
776
+
615
777
  window.requestAnimationFrame(function() {
778
+ if (resizeBuildSuppressed(scroll)) return;
779
+
616
780
  initializeFixedHeaderForScroll(scroll);
617
781
  });
618
782
  });
@@ -721,6 +885,11 @@
721
885
  document.addEventListener('turbo:load', initializeFromDocument);
722
886
  window.addEventListener('resize', initializeFromDocument);
723
887
 
888
+ window.YummyGuideAdministrateStickyTableHeaders = {
889
+ refreshColumnWidth: refreshColumnWidth,
890
+ refreshTable: refreshTable
891
+ };
892
+
724
893
  if (window.MutationObserver) {
725
894
  var mutationObserver = new MutationObserver(function(mutations) {
726
895
  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.2"
6
6
  end
7
7
  end
@@ -0,0 +1,183 @@
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
+ # ハンドルのクリックやダブルクリックで、ドラッグ完了扱いの幅適用が予約されないことを静的に確認する
63
+ it "does not apply a width when the pointer did not move" do
64
+ expect(javascript_source).to include("var shouldApplyWidth = dragState.moved && !pointerCancelled")
65
+ expect(javascript_source).to include("stopDragging(!shouldApplyWidth)")
66
+ expect(javascript_source).to include("if (!shouldApplyWidth) return")
67
+ end
68
+
69
+ # ダブルクリックの幅リセット前に、未実行の幅適用予約とプレビューを破棄することを静的に確認する
70
+ it "cancels pending width application before resetting a column" do
71
+ expect(javascript_source).to include("function cancelPendingWidthApply()")
72
+ expect(javascript_source).to include("window.cancelAnimationFrame(widthApplyFrame)")
73
+ expect(javascript_source).to include("removePreview(pendingWidthApply.preview)")
74
+ expect(javascript_source).to include("pendingWidthApply = null")
75
+ expect(javascript_source).to include("cancelPendingWidthApply()")
76
+ end
77
+
78
+ # ドラッグ中の調整後幅を半透明カラムで表示するCSSがあることを確認する
79
+ it "defines a translucent column preview" do
80
+ expect(stylesheet_source).to include(".admin-column-resizer__preview")
81
+ expect(stylesheet_source).to include("pointer-events: none")
82
+ expect(stylesheet_source).to include("will-change: width")
83
+ end
84
+
85
+ # 幅調整中のpxラベルが表示されないことを静的に確認する
86
+ it "does not render a pixel width label in the preview" do
87
+ expect(javascript_source).not_to include("PREVIEW_LABEL_CLASS")
88
+ expect(javascript_source).not_to include("admin-column-resizer__preview-label")
89
+ expect(javascript_source).not_to include("Math.round(width) + 'px'")
90
+ expect(stylesheet_source).not_to include(".admin-column-resizer__preview-label")
91
+ end
92
+
93
+ # 固定列ヘッダーより幅調整ハンドルを背面にすることを静的に確認する
94
+ it "keeps resize handles behind sticky column headers" do
95
+ expect(stylesheet_source).to include("z-index: 1")
96
+ expect(stylesheet_source).not_to include("z-index: 20")
97
+ end
98
+
99
+ # 幅の適用中だけ待機カーソルを表示することを静的に確認する
100
+ it "shows a wait cursor while applying the final width" do
101
+ expect(javascript_source).to include("APPLYING_BODY_CLASS = 'admin-column-resizer--applying'")
102
+ expect(javascript_source).to include("function startApplyingWidth()")
103
+ expect(javascript_source).to include("document.body.classList.add(APPLYING_BODY_CLASS)")
104
+ expect(javascript_source).to include("document.body.classList.remove(APPLYING_BODY_CLASS)")
105
+ expect(stylesheet_source).to include(".admin-column-resizer--applying")
106
+ expect(stylesheet_source).to include("cursor: wait !important")
107
+ end
108
+
109
+ # モバイルのtouch/pen操作でハンドル外へ移動しても調整を継続できることを静的に確認する
110
+ it "supports touch and pen pointer dragging on mobile" do
111
+ expect(javascript_source).to include("event.pointerType !== 'mouse' || event.button === 0")
112
+ expect(javascript_source).to include("event.isPrimary === false")
113
+ expect(javascript_source).to include("handle.setPointerCapture(event.pointerId)")
114
+ expect(javascript_source).to include("dragState.handle.releasePointerCapture(dragState.pointerId)")
115
+ expect(stylesheet_source).to include("right: -14px")
116
+ expect(stylesheet_source).to include("width: 36px")
117
+ end
118
+
119
+ # 幅適用後の固定ヘッダー追従が全体rebuildではなく対象カラム同期を優先することを静的に確認する
120
+ it "uses targeted fixed header synchronization after applying a width" do
121
+ expect(javascript_source).to include("window.YummyGuideAdministrateStickyTableHeaders")
122
+ expect(javascript_source).to include("api.refreshColumnWidth({")
123
+ expect(sticky_table_headers_source).to include("window.YummyGuideAdministrateStickyTableHeaders = {")
124
+ expect(sticky_table_headers_source).to include("refreshColumnWidth: refreshColumnWidth")
125
+ expect(sticky_table_headers_source).to include("refreshTable: refreshTable")
126
+ expect(sticky_table_headers_source).to include("function refreshColumnWidth(options)")
127
+ expect(sticky_table_headers_source).to include("applySourceColumnMinWidth(sourceTable, columnIndex, columnCount, width)")
128
+ expect(sticky_table_headers_source).to include("applyFixedHeaderCellWidth(fixedTable, columnIndex, width)")
129
+ expect(sticky_table_headers_source).to include("suppressResizeBuild(scroll)")
130
+ end
131
+
132
+ # 幅適用後の固定左列追従もResizeObserverの重複再計算に頼らないことを静的に確認する
133
+ it "refreshes sticky left columns directly after applying a width" do
134
+ expect(javascript_source).to include("window.YummyGuideAdministrateStickyLeftColumns")
135
+ expect(javascript_source).to include("api.refreshColumnWidth({")
136
+ expect(javascript_source).to include("sourceTable: pendingWidth.sourceTable")
137
+ expect(javascript_source).to include("columnId: pendingWidth.columnId")
138
+ expect(javascript_source).to include("width: pendingWidth.width")
139
+ expect(javascript_source).to include("api.refreshTable(sourceTable)")
140
+ expect(javascript_source).to include("refreshStickyLeftColumns(pendingWidth.sourceTable)")
141
+ expect(sticky_left_columns_source).to include("window.YummyGuideAdministrateStickyLeftColumns = {")
142
+ expect(sticky_left_columns_source).to include("refreshColumnWidth: refreshColumnWidth")
143
+ expect(sticky_left_columns_source).to include("refreshTable: refreshTable")
144
+ expect(sticky_left_columns_source).to include("function refreshTable(table)")
145
+ expect(sticky_left_columns_source).to include("function refreshColumnWidth(options)")
146
+ expect(sticky_left_columns_source).to include("suppressResizeApply(table)")
147
+ end
148
+
149
+ # 固定5列の4列目を調整した直後に5列目のleftが古い幅で残らないことを静的に確認する
150
+ it "uses the resized fixed column width when recalculating sticky-left offsets" do
151
+ expect(sticky_left_columns_source).to include("function widthOverride(headerCells, options)")
152
+ expect(sticky_left_columns_source).to include("columnIndex(headerCells, settings.columnId)")
153
+ expect(sticky_left_columns_source).to include("width: preciseNumber(width)")
154
+ expect(sticky_left_columns_source).to include("var hasMeasuredWidth = widths.some(Boolean)")
155
+ expect(sticky_left_columns_source).to include("applyWidthOverride(widths, override)")
156
+ expect(sticky_left_columns_source).to include("var offsets = stickyOffsets(table, count, options)")
157
+ end
158
+
159
+ # 幅リセット時は明示幅がないため、クリア後に通常の固定左列再計算へ戻すことを静的に確認する
160
+ it "recalculates sticky-left offsets after clearing a column width" do
161
+ expect(javascript_source).to include("clearColumnWidth(columnId, key)")
162
+ expect(javascript_source).to include("refreshStickyHeaderTable(sourceTable, function()")
163
+ expect(javascript_source).to include("refreshStickyLeftColumns(sourceTable)")
164
+ end
165
+
166
+ # ダブルクリックで幅リセットした時も、固定列同期が完了するまで待機カーソルを表示することを静的に確認する
167
+ it "shows a wait cursor while resetting a column width" do
168
+ expect(javascript_source).to include("clearColumnWidth(columnId, key)")
169
+ expect(javascript_source).to include("startApplyingWidth()")
170
+ expect(javascript_source).to include("stopApplyingWidth(null)")
171
+ expect(javascript_source).to include("scheduleStickyRefresh(function()")
172
+ end
173
+
174
+ # 幅リセット時は固定ヘッダーの管理幅を先に作り直してから固定左列を再計算することを静的に確認する
175
+ it "rebuilds fixed headers before refreshing sticky-left columns after clearing a width" do
176
+ expect(javascript_source).to include("function refreshStickyHeaderTable(sourceTable, callback)")
177
+ expect(javascript_source).to include("api.refreshTable(sourceTable, callback)")
178
+ expect(javascript_source).to include("scheduleStickyRefresh(callback)")
179
+ expect(sticky_table_headers_source).to include("function refreshTable(sourceTable, callback)")
180
+ expect(sticky_table_headers_source).to include("initializeFixedHeaderForScroll(scroll)")
181
+ expect(sticky_table_headers_source).to include("window.requestAnimationFrame(callback)")
182
+ end
183
+ 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.2
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