yummy-guide-generic-administrate 0.7.1 → 0.8.0

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: a0a5f41bc5f403a7d4da80d9a2b66c7313a9370374e171d2916b96de8aea2a8d
4
- data.tar.gz: 77c5494b19b7c2364477e394c954e5640ac737658e05ec481ecef62aafc30c59
3
+ metadata.gz: b23f3b7fd7d17420fdeaf7325f51f1f70b93fb86497bcdb6621b617b5086d926
4
+ data.tar.gz: 38004b14e38a03b882d6e84253fc63ad1a2e8480ab287ee27ced1d13e81239a7
5
5
  SHA512:
6
- metadata.gz: 73ea4e2fb99ffc3e16393fc105e93b39e0812ad313f4e88f95a333ae18a10132e7207e482c956f37dad5391c8ceb6ab246724adc6e413fe8d5b012c3d45c03fc
7
- data.tar.gz: e891e0b581251db97ac188c934bef264d77790b47b154e44dd57f4d82cc57e4bd1f8d5a48e1537cf710e0e585935ff10eb889c3bac690a1455d16477b979b069
6
+ metadata.gz: ecc7d99ccc8f23f5b4d0dd9288a9d6f870f95dc9c2d19256865828fa9a3d1f10d322fd37d627d2e85d648e13ab1fb128fb2d0430035451147fa2f4b72739bbb4
7
+ data.tar.gz: 05b2097517ad48edd1e8f54230bd3f2d1ea5e591dccc733d0397e1ec89d7d3b855920066296b4119c6c4b7b46572413ca07730dae2663e584cf403d1bc0f6cc9
data/README.md CHANGED
@@ -138,7 +138,8 @@ engine の共通 partial に委譲します。
138
138
 
139
139
  gem 付属の collection partial をそのまま使う場合、table wrapper と table 本体に
140
140
  必要な `data-*` 属性はすでに入っています。そのため、JS / CSS を読み込めば固定
141
- ヘッダーは自動で有効になります。
141
+ ヘッダーとカラム幅調整は自動で有効になります。カラム幅調整を使う場合は
142
+ `yummy_guide_administrate/column_resizer.js` も読み込んでください。
142
143
 
143
144
  内部的には以下のような構造になります。
144
145
 
@@ -154,6 +155,8 @@ gem 付属の collection partial をそのまま使う場合、table wrapper と
154
155
  </div>
155
156
  ```
156
157
 
158
+ 複数画面で幅設定を共有したい場合は、render local に `column_width_storage_scope: "admin.reservations"` のような任意の scope 名を渡してください。
159
+
157
160
  #### 2. ヘッダー位置を明示したい場合
158
161
 
159
162
  固定ヘッダーの表示位置をページ上部の特定箇所に合わせたい場合は、
@@ -0,0 +1,610 @@
1
+ (function() {
2
+ var TABLE_SELECTOR = 'table[data-fixed-columns-count]';
3
+ var HANDLE_CLASS = 'admin-column-resizer__handle';
4
+ var HEADER_CLASS = 'admin-column-resizer__header';
5
+ var TABLE_CLASS = 'admin-column-resizer__table';
6
+ var DRAGGING_BODY_CLASS = 'admin-column-resizer--dragging';
7
+ var FIXED_HEADER_TABLE_CLASS = 'table-fixed-header__table';
8
+ var STORAGE_PREFIX = 'yummyGuideAdminColumnWidths:v1:';
9
+ var STYLE_ELEMENT_ID = 'admin-column-resizer-rules';
10
+ var WIDTH_VAR_PREFIX = '--admin-column-resizer-col-';
11
+ var MIN_WIDTH = 48;
12
+ var STICKY_REFRESH_INTERVAL = 120;
13
+
14
+ var dragState = null;
15
+ var dragApplyFrame = null;
16
+ var generatedRuleCount = 0;
17
+ var initializedHandles = new WeakSet();
18
+ var tableStates = new WeakMap();
19
+ var stickyRefreshFrame = null;
20
+ var stickyRefreshTimer = null;
21
+ var lastStickyRefreshAt = 0;
22
+
23
+ function storageScopeForTable(table) {
24
+ var sourceTable = matchingSourceTable(table);
25
+ var scopedTable = sourceTable || table;
26
+ var scope = scopedTable && scopedTable.getAttribute('data-column-resizer-storage-scope');
27
+
28
+ return scope || window.location.pathname;
29
+ }
30
+
31
+ function storageKeyForTable(table) {
32
+ return STORAGE_PREFIX + storageScopeForTable(table);
33
+ }
34
+
35
+ function parsedWidths(rawWidths) {
36
+ if (!rawWidths) return {};
37
+
38
+ var widths = JSON.parse(rawWidths);
39
+ return widths && typeof widths === 'object' && !Array.isArray(widths) ? widths : {};
40
+ }
41
+
42
+ function safeReadWidths(key) {
43
+ try {
44
+ return parsedWidths(window.localStorage.getItem(key));
45
+ } catch (_error) {
46
+ return {};
47
+ }
48
+ }
49
+
50
+ function safeWriteWidths(key, widths) {
51
+ try {
52
+ if (Object.keys(widths).length === 0) {
53
+ window.localStorage.removeItem(key);
54
+ } else {
55
+ window.localStorage.setItem(key, JSON.stringify(widths));
56
+ }
57
+ } catch (_error) {
58
+ // localStorage may be unavailable in private browsing or restricted contexts.
59
+ }
60
+ }
61
+
62
+ function preciseNumber(value) {
63
+ return Math.round(value * 1000) / 1000;
64
+ }
65
+
66
+ function cssPixelValue(value) {
67
+ return preciseNumber(value) + 'px';
68
+ }
69
+
70
+ function measuredWidth(element) {
71
+ if (!element) return 0;
72
+
73
+ var rectWidth = element.getBoundingClientRect().width;
74
+ return preciseNumber(rectWidth || element.offsetWidth || 0);
75
+ }
76
+
77
+ function normalizedIdentifier(value) {
78
+ return (value || '').toString().trim().toLowerCase()
79
+ .replace(/[^a-z0-9]+/g, '_')
80
+ .replace(/^_+|_+$/g, '')
81
+ .slice(0, 80) || 'column';
82
+ }
83
+
84
+ function directCells(row) {
85
+ return Array.from(row.children).filter(function(cell) {
86
+ return cell.tagName === 'TH' || cell.tagName === 'TD';
87
+ });
88
+ }
89
+
90
+ function columnHeaders(table) {
91
+ var row = table.querySelector('thead tr');
92
+ if (!row) return [];
93
+
94
+ return directCells(row).filter(function(cell) {
95
+ return cell.tagName === 'TH';
96
+ });
97
+ }
98
+
99
+ function headerLabel(header) {
100
+ return (header.getAttribute('aria-label') || header.getAttribute('title') || header.textContent || '')
101
+ .trim()
102
+ .replace(/\s+/g, ' ');
103
+ }
104
+
105
+ function headerSignature(table) {
106
+ return columnHeaders(table).map(function(header, index) {
107
+ return normalizedIdentifier(header.dataset.columnId || headerLabel(header) || ('column_' + (index + 1)));
108
+ }).join('__');
109
+ }
110
+
111
+ function matchingSourceTable(table) {
112
+ if (!table) return null;
113
+ if (!table.classList.contains(FIXED_HEADER_TABLE_CLASS)) return null;
114
+
115
+ var signature = headerSignature(table);
116
+ return sourceTables().find(function(sourceTable) {
117
+ return headerSignature(sourceTable) === signature;
118
+ }) || null;
119
+ }
120
+
121
+ function tableIdentifier(table) {
122
+ if (table.dataset.adminColumnResizerTableId) return table.dataset.adminColumnResizerTableId;
123
+
124
+ var sourceTable = matchingSourceTable(table);
125
+ if (sourceTable) {
126
+ table.dataset.adminColumnResizerTableId = tableIdentifier(sourceTable);
127
+ return table.dataset.adminColumnResizerTableId;
128
+ }
129
+
130
+ var tableLabel = table.id ||
131
+ table.getAttribute('aria-labelledby') ||
132
+ table.getAttribute('data-admin-column-resizer-table') ||
133
+ table.getAttribute('data-fixed-header-source');
134
+
135
+ if (!tableLabel) {
136
+ var index = sourceTables().indexOf(table);
137
+ tableLabel = 'table_' + (index >= 0 ? index + 1 : allTrackedTables().indexOf(table) + 1);
138
+ }
139
+
140
+ table.dataset.adminColumnResizerTableId = normalizedIdentifier(tableLabel);
141
+ return table.dataset.adminColumnResizerTableId;
142
+ }
143
+
144
+ function fallbackColumnId(table, header, index) {
145
+ return [
146
+ 'fallback',
147
+ tableIdentifier(table),
148
+ index + 1,
149
+ normalizedIdentifier(headerLabel(header))
150
+ ].join('.');
151
+ }
152
+
153
+ function headerColumnId(header) {
154
+ return header.dataset.adminColumnResizerColumnId || header.dataset.columnId || '';
155
+ }
156
+
157
+ function assignHeaderColumnId(table, header, index) {
158
+ if (header.colSpan && header.colSpan > 1) return '';
159
+
160
+ var columnId = header.dataset.columnId || fallbackColumnId(table, header, index);
161
+ header.dataset.adminColumnResizerColumnId = columnId;
162
+ return columnId;
163
+ }
164
+
165
+ function columnWidthVariable(index) {
166
+ return WIDTH_VAR_PREFIX + (index + 1);
167
+ }
168
+
169
+ function columnRule(index) {
170
+ var nthChild = index + 1;
171
+ 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(', ');
177
+
178
+ return selector + ' { box-sizing: border-box !important; width: var(' + variableName + ') !important; min-width: var(' + variableName + ') !important; max-width: var(' + variableName + ') !important; }';
179
+ }
180
+
181
+ function ensureStyleElement() {
182
+ var styleElement = document.getElementById(STYLE_ELEMENT_ID);
183
+ if (styleElement) return styleElement;
184
+
185
+ styleElement = document.createElement('style');
186
+ styleElement.id = STYLE_ELEMENT_ID;
187
+ document.head.appendChild(styleElement);
188
+
189
+ return styleElement;
190
+ }
191
+
192
+ function ensureColumnRules(columnCount) {
193
+ if (columnCount <= generatedRuleCount) return;
194
+
195
+ var rules = [];
196
+ for (var index = generatedRuleCount; index < columnCount; index += 1) {
197
+ rules.push(columnRule(index));
198
+ }
199
+
200
+ ensureStyleElement().appendChild(document.createTextNode(rules.join('\n') + '\n'));
201
+ generatedRuleCount = columnCount;
202
+ }
203
+
204
+ function tablesFromRoot(root) {
205
+ var tables = [];
206
+
207
+ function appendTable(table) {
208
+ if (table && tables.indexOf(table) === -1) {
209
+ tables.push(table);
210
+ }
211
+ }
212
+
213
+ if (root.closest) {
214
+ appendTable(root.closest(TABLE_SELECTOR));
215
+ }
216
+
217
+ if (root.matches && root.matches(TABLE_SELECTOR)) {
218
+ appendTable(root);
219
+ }
220
+
221
+ if (root.querySelectorAll) {
222
+ root.querySelectorAll(TABLE_SELECTOR).forEach(appendTable);
223
+ }
224
+
225
+ return tables;
226
+ }
227
+
228
+ function shouldInitializeForAddedNode(node) {
229
+ if (!node.matches) return false;
230
+ if (node.matches(TABLE_SELECTOR) || (node.querySelector && node.querySelector(TABLE_SELECTOR))) return true;
231
+ if (!node.closest || !node.closest(TABLE_SELECTOR)) return false;
232
+
233
+ return !!node.closest('thead') || node.matches('colgroup, col');
234
+ }
235
+
236
+ function allTrackedTables() {
237
+ return Array.from(document.querySelectorAll(TABLE_SELECTOR));
238
+ }
239
+
240
+ function sourceTables() {
241
+ return allTrackedTables().filter(function(table) {
242
+ return table.getAttribute('aria-hidden') !== 'true' && !table.classList.contains(FIXED_HEADER_TABLE_CLASS);
243
+ });
244
+ }
245
+
246
+ function stateForTable(table) {
247
+ return tableStates.get(table) || configureTable(table);
248
+ }
249
+
250
+ function ensureManagedColgroup(table, columnCount) {
251
+ var fixedHeaderColgroup = table.querySelector('colgroup[data-fixed-header-colgroup]');
252
+ var colgroup = fixedHeaderColgroup || table.querySelector('colgroup[data-admin-column-resizer-colgroup]');
253
+
254
+ if (!colgroup) {
255
+ colgroup = document.createElement('colgroup');
256
+ colgroup.setAttribute('data-admin-column-resizer-colgroup', 'true');
257
+ table.insertBefore(colgroup, table.firstChild);
258
+ }
259
+
260
+ while (colgroup.children.length < columnCount) {
261
+ colgroup.appendChild(document.createElement('col'));
262
+ }
263
+
264
+ return colgroup;
265
+ }
266
+
267
+ function configureTable(table) {
268
+ var headers = columnHeaders(table);
269
+ if (headers.length === 0) return null;
270
+
271
+ var indexByColumnId = Object.create(null);
272
+
273
+ headers.forEach(function(header, index) {
274
+ var columnId = assignHeaderColumnId(table, header, index);
275
+ if (!columnId) return;
276
+
277
+ if (indexByColumnId[columnId] === undefined) {
278
+ indexByColumnId[columnId] = index;
279
+ }
280
+
281
+ ensureHandle(header);
282
+ });
283
+
284
+ table.classList.add(TABLE_CLASS);
285
+ ensureColumnRules(headers.length);
286
+ ensureManagedColgroup(table, headers.length);
287
+
288
+ var state = {
289
+ columnCount: headers.length,
290
+ indexByColumnId: indexByColumnId
291
+ };
292
+ tableStates.set(table, state);
293
+
294
+ return state;
295
+ }
296
+
297
+ function columnIndex(table, columnId) {
298
+ var state = stateForTable(table);
299
+ if (!state || state.indexByColumnId[columnId] === undefined) return -1;
300
+
301
+ return state.indexByColumnId[columnId];
302
+ }
303
+
304
+ function columnHeader(table, columnId) {
305
+ var index = columnIndex(table, columnId);
306
+ if (index < 0) return null;
307
+
308
+ return columnHeaders(table)[index] || null;
309
+ }
310
+
311
+ function applyColgroupWidth(table, columnCount, index, widthValue) {
312
+ var colgroup = ensureManagedColgroup(table, columnCount);
313
+ if (!colgroup || !colgroup.children[index]) return;
314
+
315
+ colgroup.children[index].style.width = widthValue;
316
+ }
317
+
318
+ function clearColgroupWidth(table, columnCount, index) {
319
+ var colgroup = ensureManagedColgroup(table, columnCount);
320
+ if (!colgroup || !colgroup.children[index]) return;
321
+
322
+ colgroup.children[index].style.removeProperty('width');
323
+ }
324
+
325
+ function applyTableColumnWidth(table, columnId, width) {
326
+ var state = stateForTable(table);
327
+ var index = state && state.indexByColumnId[columnId];
328
+ if (!state || index === undefined) return;
329
+
330
+ var widthValue = cssPixelValue(width);
331
+ table.style.setProperty(columnWidthVariable(index), widthValue);
332
+ applyColgroupWidth(table, state.columnCount, index, widthValue);
333
+ }
334
+
335
+ function clearTableColumnWidth(table, columnId) {
336
+ var state = stateForTable(table);
337
+ var index = state && state.indexByColumnId[columnId];
338
+ if (!state || index === undefined) return;
339
+
340
+ table.style.removeProperty(columnWidthVariable(index));
341
+ clearColgroupWidth(table, state.columnCount, index);
342
+ }
343
+
344
+ function applyColumnWidth(columnId, width, key) {
345
+ allTrackedTables().forEach(function(table) {
346
+ if (key && storageKeyForTable(table) !== key) return;
347
+
348
+ applyTableColumnWidth(table, columnId, width);
349
+ });
350
+ }
351
+
352
+ function clearColumnWidth(columnId, key) {
353
+ allTrackedTables().forEach(function(table) {
354
+ if (key && storageKeyForTable(table) !== key) return;
355
+
356
+ clearTableColumnWidth(table, columnId);
357
+ });
358
+ }
359
+
360
+ function applyStoredWidthsToTable(table, widths) {
361
+ Object.keys(widths).forEach(function(columnId) {
362
+ var width = parseFloat(widths[columnId]);
363
+ if (Number.isNaN(width) || width < MIN_WIDTH) return;
364
+
365
+ applyTableColumnWidth(table, columnId, width);
366
+ });
367
+ }
368
+
369
+ function applyStoredWidthsToTables(tables) {
370
+ if (dragState) return;
371
+
372
+ var widthsByStorageKey = Object.create(null);
373
+
374
+ tables.forEach(function(table) {
375
+ var key = storageKeyForTable(table);
376
+ widthsByStorageKey[key] = widthsByStorageKey[key] || safeReadWidths(key);
377
+
378
+ applyStoredWidthsToTable(table, widthsByStorageKey[key]);
379
+ });
380
+ }
381
+
382
+ function dispatchStickyRefresh() {
383
+ stickyRefreshFrame = null;
384
+ lastStickyRefreshAt = Date.now();
385
+ window.dispatchEvent(new Event('resize'));
386
+ }
387
+
388
+ function scheduleStickyRefresh(immediate) {
389
+ if (stickyRefreshFrame || stickyRefreshTimer) return;
390
+
391
+ var elapsed = Date.now() - lastStickyRefreshAt;
392
+ var delay = immediate ? 0 : Math.max(0, STICKY_REFRESH_INTERVAL - elapsed);
393
+
394
+ stickyRefreshTimer = window.setTimeout(function() {
395
+ stickyRefreshTimer = null;
396
+ stickyRefreshFrame = window.requestAnimationFrame(dispatchStickyRefresh);
397
+ }, delay);
398
+ }
399
+
400
+ function columnNeedsDragRefresh(header) {
401
+ return header.classList.contains('sticky') ||
402
+ header.classList.contains('sticky-left') ||
403
+ header.classList.contains('actions-column');
404
+ }
405
+
406
+ function sourceTableForHandle(handle) {
407
+ var header = handle.closest('th');
408
+ if (!header) return null;
409
+
410
+ var table = handle.closest(TABLE_SELECTOR);
411
+ var columnId = headerColumnId(header);
412
+
413
+ if (table && table.getAttribute('aria-hidden') !== 'true' && !table.classList.contains(FIXED_HEADER_TABLE_CLASS)) {
414
+ return table;
415
+ }
416
+
417
+ return sourceTables().find(function(sourceTable) {
418
+ return columnHeader(sourceTable, columnId);
419
+ }) || matchingSourceTable(table) || null;
420
+ }
421
+
422
+ function flushDragWidth() {
423
+ if (!dragState) return;
424
+
425
+ if (dragApplyFrame) {
426
+ window.cancelAnimationFrame(dragApplyFrame);
427
+ dragApplyFrame = null;
428
+ }
429
+
430
+ applyColumnWidth(dragState.columnId, dragState.currentWidth || dragState.startWidth, dragState.storageKey);
431
+ }
432
+
433
+ function scheduleDragWidth(width) {
434
+ dragState.currentWidth = width;
435
+ dragState.moved = dragState.moved || Math.abs(width - dragState.startWidth) > 2;
436
+
437
+ if (dragApplyFrame) return;
438
+
439
+ dragApplyFrame = window.requestAnimationFrame(function() {
440
+ dragApplyFrame = null;
441
+ if (!dragState) return;
442
+
443
+ applyColumnWidth(dragState.columnId, dragState.currentWidth, dragState.storageKey);
444
+ if (dragState.refreshDuringDrag) scheduleStickyRefresh(false);
445
+ });
446
+ }
447
+
448
+ function startDrag(event) {
449
+ if (event.button !== 0) return;
450
+
451
+ var handle = event.currentTarget;
452
+ var header = handle.closest('th');
453
+ if (!header) return;
454
+
455
+ var columnId = headerColumnId(header);
456
+ if (!columnId) return;
457
+
458
+ var sourceTable = sourceTableForHandle(handle);
459
+ if (!sourceTable) return;
460
+
461
+ var sourceHeader = columnHeader(sourceTable, columnId) || header;
462
+ var startWidth = measuredWidth(sourceHeader) || measuredWidth(header);
463
+ if (!startWidth) return;
464
+
465
+ event.preventDefault();
466
+ event.stopPropagation();
467
+
468
+ dragState = {
469
+ columnId: columnId,
470
+ storageKey: storageKeyForTable(sourceTable),
471
+ startX: event.clientX,
472
+ startWidth: startWidth,
473
+ currentWidth: startWidth,
474
+ moved: false,
475
+ refreshDuringDrag: columnNeedsDragRefresh(sourceHeader)
476
+ };
477
+
478
+ document.body.classList.add(DRAGGING_BODY_CLASS);
479
+ document.addEventListener('pointermove', handleDragMove);
480
+ document.addEventListener('pointerup', finishDrag);
481
+ document.addEventListener('pointercancel', finishDrag);
482
+ }
483
+
484
+ function handleDragMove(event) {
485
+ if (!dragState) return;
486
+
487
+ event.preventDefault();
488
+
489
+ scheduleDragWidth(Math.max(MIN_WIDTH, dragState.startWidth + event.clientX - dragState.startX));
490
+ }
491
+
492
+ function finishDrag(event) {
493
+ if (!dragState) return;
494
+
495
+ if (event) {
496
+ event.preventDefault();
497
+ }
498
+
499
+ flushDragWidth();
500
+
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);
505
+
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);
512
+ }
513
+
514
+ function resetColumn(event) {
515
+ var handle = event.currentTarget;
516
+ var header = handle.closest('th');
517
+ if (!header) return;
518
+
519
+ var columnId = headerColumnId(header);
520
+ if (!columnId) return;
521
+
522
+ event.preventDefault();
523
+ event.stopPropagation();
524
+
525
+ var sourceTable = sourceTableForHandle(handle);
526
+ var key = storageKeyForTable(sourceTable || handle.closest(TABLE_SELECTOR));
527
+ var widths = safeReadWidths(key);
528
+ delete widths[columnId];
529
+ safeWriteWidths(key, widths);
530
+ clearColumnWidth(columnId, key);
531
+ scheduleStickyRefresh(true);
532
+ }
533
+
534
+ function stopHandleClick(event) {
535
+ event.preventDefault();
536
+ event.stopPropagation();
537
+ }
538
+
539
+ function ensureHandle(header) {
540
+ if (!headerColumnId(header)) return;
541
+
542
+ header.classList.add(HEADER_CLASS);
543
+
544
+ var handle = Array.from(header.children).find(function(child) {
545
+ return child.classList && child.classList.contains(HANDLE_CLASS);
546
+ });
547
+
548
+ if (!handle) {
549
+ handle = document.createElement('span');
550
+ handle.className = HANDLE_CLASS;
551
+ handle.setAttribute('aria-hidden', 'true');
552
+ header.appendChild(handle);
553
+ }
554
+
555
+ if (initializedHandles.has(handle)) return;
556
+
557
+ initializedHandles.add(handle);
558
+ handle.addEventListener('pointerdown', startDrag);
559
+ handle.addEventListener('dblclick', resetColumn);
560
+ handle.addEventListener('click', stopHandleClick);
561
+ }
562
+
563
+ function initializeTable(table) {
564
+ return configureTable(table);
565
+ }
566
+
567
+ function initializeColumnResizer(root) {
568
+ if (!root.querySelectorAll) return;
569
+
570
+ var configuredTables = tablesFromRoot(root).filter(function(table) {
571
+ return !!initializeTable(table);
572
+ });
573
+
574
+ applyStoredWidthsToTables(configuredTables);
575
+ }
576
+
577
+ function initializeFromDocument() {
578
+ initializeColumnResizer(document);
579
+ }
580
+
581
+ if (document.readyState === 'loading') {
582
+ document.addEventListener('DOMContentLoaded', initializeFromDocument);
583
+ } else {
584
+ initializeFromDocument();
585
+ }
586
+
587
+ document.addEventListener('turbo:load', initializeFromDocument);
588
+ window.addEventListener('resize', initializeFromDocument);
589
+
590
+ if (window.MutationObserver) {
591
+ var mutationObserver = new MutationObserver(function(mutations) {
592
+ mutations.forEach(function(mutation) {
593
+ mutation.addedNodes.forEach(function(node) {
594
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
595
+ if (!shouldInitializeForAddedNode(node)) return;
596
+
597
+ initializeColumnResizer(node);
598
+ });
599
+ });
600
+ });
601
+
602
+ mutationObserver.observe(document.documentElement, {
603
+ childList: true,
604
+ subtree: true
605
+ });
606
+ }
607
+
608
+ setTimeout(initializeFromDocument, 100);
609
+ setTimeout(initializeFromDocument, 300);
610
+ })();
@@ -0,0 +1,64 @@
1
+ .admin-column-resizer__header {
2
+ position: relative;
3
+ }
4
+
5
+ .admin-column-resizer__handle {
6
+ position: absolute;
7
+ top: 0;
8
+ right: -5px;
9
+ bottom: 0;
10
+ z-index: 20;
11
+ width: 12px;
12
+ cursor: col-resize;
13
+ touch-action: none;
14
+ }
15
+
16
+ .admin-column-resizer__handle::after {
17
+ content: "";
18
+ position: absolute;
19
+ top: 20%;
20
+ right: 3px;
21
+ bottom: 20%;
22
+ width: 1px;
23
+ background: rgba(255, 255, 255, 0.45);
24
+ opacity: 0;
25
+ transition: opacity 0.15s ease, background-color 0.15s ease;
26
+ }
27
+
28
+ .admin-column-resizer__header:hover > .admin-column-resizer__handle::after,
29
+ .admin-column-resizer__handle:focus-visible::after,
30
+ .admin-column-resizer--dragging .admin-column-resizer__handle::after {
31
+ opacity: 1;
32
+ }
33
+
34
+ .admin-column-resizer__handle:hover::after,
35
+ .admin-column-resizer__handle:focus-visible::after {
36
+ background: rgba(255, 255, 255, 0.8);
37
+ }
38
+
39
+ .admin-column-resizer--dragging,
40
+ .admin-column-resizer--dragging * {
41
+ cursor: col-resize !important;
42
+ user-select: none !important;
43
+ }
44
+
45
+ [data-fixed-table-header] .admin-column-resizer__handle {
46
+ pointer-events: auto;
47
+ }
48
+
49
+ [data-fixed-table-header] .table-fixed-header__table th.admin-column-resizer__header:not(.sticky):not(.sticky-left) {
50
+ position: relative;
51
+ }
52
+
53
+ @media (pointer: coarse), screen and (max-width: 767px) {
54
+ .admin-column-resizer__handle {
55
+ right: -10px;
56
+ width: 28px;
57
+ }
58
+
59
+ .admin-column-resizer__handle::after {
60
+ right: 13px;
61
+ background: rgba(255, 255, 255, 0.75);
62
+ opacity: 1;
63
+ }
64
+ }
@@ -48,7 +48,7 @@
48
48
  z-index: 2;
49
49
 
50
50
  &::after {
51
- background-color: transparent;
51
+ background-color: #d1d5db;
52
52
  bottom: 0;
53
53
  content: "";
54
54
  left: 3px;
@@ -59,10 +59,14 @@
59
59
  }
60
60
 
61
61
  &:hover::after {
62
- background-color: #9aa8ba;
62
+ background-color: #4b5563;
63
63
  }
64
64
  }
65
65
 
66
+ .admin-navigation--resizing .admin-navigation-resize-handle::after {
67
+ background-color: #374151;
68
+ }
69
+
66
70
  .admin-navigation-scroll-area {
67
71
  max-height: inherit;
68
72
  overflow-x: hidden;
@@ -70,10 +74,6 @@
70
74
  overscroll-behavior: contain;
71
75
  }
72
76
 
73
- .admin-navigation--resizing .admin-navigation-resize-handle::after {
74
- background-color: #5d7596;
75
- }
76
-
77
77
  .admin-navigation-scroll-area .menubar,
78
78
  .admin-navigation-scroll-area .menubar a,
79
79
  .admin-navigation-scroll-area .menubar__group-label,
@@ -99,6 +99,10 @@
99
99
  width: 100%;
100
100
  }
101
101
 
102
+ .admin-navigation-scroll-area .navigation__link--indented {
103
+ text-indent: 16px;
104
+ }
105
+
102
106
  .admin-navigation-scroll-area .menubar li.menubar__item a {
103
107
  padding-left: 36px;
104
108
  }
@@ -1,5 +1,6 @@
1
1
  @import "fixed_submit_actions";
2
2
  @import "datetime_input";
3
+ @import "column_resizer";
3
4
 
4
5
  @media screen and (min-width: 768px) {
5
6
  .app-container {
@@ -26,6 +26,17 @@ module YummyGuide
26
26
  0
27
27
  end
28
28
 
29
+ def yummy_guide_administrate_collection_column_id(collection_presenter, column_name)
30
+ [
31
+ collection_presenter.resource_name,
32
+ column_name
33
+ ].map { |segment| segment.to_s.parameterize(separator: "_") }.join(".")
34
+ end
35
+
36
+ def yummy_guide_administrate_collection_actions_column_id(collection_presenter)
37
+ yummy_guide_administrate_collection_column_id(collection_presenter, :actions)
38
+ end
39
+
29
40
  def yummy_guide_administrate_build_collection_cell(content:, present_path: nil, target: nil, reference_link: false, text_link: false, leading_actions: nil, copy_text: nil, copy_text_transform: nil)
30
41
  normalized_content = yummy_guide_administrate_collection_cell_content(content)
31
42
 
@@ -1,8 +1,12 @@
1
+ <% column_width_storage_scope = local_assigns[:column_width_storage_scope].presence %>
1
2
  <div class="scroll-table" data-fixed-header-scroll>
2
3
  <table
3
4
  aria-labelledby="<%= table_title %>"
4
5
  data-fixed-columns-count="<%= yummy_guide_administrate_collection_table_fixed_columns_count(page: page, collection_presenter: collection_presenter) %>"
5
6
  data-mobile-fixed-columns-count="<%= yummy_guide_administrate_collection_table_mobile_fixed_columns_count(page: page, collection_presenter: collection_presenter) %>"
7
+ <% if column_width_storage_scope %>
8
+ data-column-resizer-storage-scope="<%= column_width_storage_scope %>"
9
+ <% end %>
6
10
  data-fixed-header-source
7
11
  >
8
12
  <thead>
@@ -12,6 +16,7 @@
12
16
  class="cell-label cell-label--<%= attr_type.html_class %> cell-label--<%= collection_presenter.ordered_html_class(attr_name) %> cell-label--<%= "#{collection_presenter.resource_name}_#{attr_name}" %>"
13
17
  scope="col"
14
18
  aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>"
19
+ data-column-id="<%= yummy_guide_administrate_collection_column_id(collection_presenter, attr_name) %>"
15
20
  >
16
21
  <%= link_to(sanitized_order_params(page, collection_field_name).merge(
17
22
  collection_presenter.order_params_for(attr_name, key: collection_field_name)
@@ -9,6 +9,7 @@ module YummyGuide
9
9
  app.config.assets.precompile += %w[
10
10
  yummy_guide_administrate/components.css
11
11
  yummy_guide_administrate/clipboards.js
12
+ yummy_guide_administrate/column_resizer.js
12
13
  yummy_guide_administrate/datetime_input.js
13
14
  yummy_guide_administrate/fixed_submit_actions.js
14
15
  yummy_guide_administrate/filter_controls.js
@@ -2,6 +2,6 @@
2
2
 
3
3
  module YummyGuide
4
4
  module Administrate
5
- VERSION = "0.7.1"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
@@ -54,6 +54,29 @@ RSpec.describe YummyGuide::Administrate::CollectionHelper do
54
54
  end
55
55
  end
56
56
 
57
+ describe "#yummy_guide_administrate_collection_column_id" do
58
+ let(:collection_presenter) { Struct.new(:resource_name).new("external_reservation") }
59
+
60
+ # 一覧カラム幅の保存に使う ID が列順ではなくリソース名と属性名から決まることを確認する
61
+ it "builds a stable id from the resource name and attribute name" do
62
+ expect(helper_host.yummy_guide_administrate_collection_column_id(collection_presenter, :customer_name)).to eq("external_reservation.customer_name")
63
+ end
64
+
65
+ # 手書き追加カラムも同じ形式の ID で保存対象にできることを確認する
66
+ it "normalizes custom column names" do
67
+ expect(helper_host.yummy_guide_administrate_collection_column_id(collection_presenter, "Article Pages")).to eq("external_reservation.article_pages")
68
+ end
69
+ end
70
+
71
+ describe "#yummy_guide_administrate_collection_actions_column_id" do
72
+ # actions 列も通常カラムと同じ保存キー体系に入ることを確認する
73
+ it "builds the actions column id" do
74
+ collection_presenter = Struct.new(:resource_name).new("reservation")
75
+
76
+ expect(helper_host.yummy_guide_administrate_collection_actions_column_id(collection_presenter)).to eq("reservation.actions")
77
+ end
78
+ end
79
+
57
80
  describe "#yummy_guide_administrate_build_collection_cell" do
58
81
  let(:present_path) { "/admin/articles/test-article" }
59
82
 
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.7.1
4
+ version: 0.8.0
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-01 00:00:00.000000000 Z
11
+ date: 2026-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: administrate
@@ -91,6 +91,7 @@ files:
91
91
  - Rakefile
92
92
  - app/assets/images/yummy_guide_administrate/icon-copy.svg
93
93
  - app/assets/javascripts/yummy_guide_administrate/clipboards.js
94
+ - app/assets/javascripts/yummy_guide_administrate/column_resizer.js
94
95
  - app/assets/javascripts/yummy_guide_administrate/datetime_input.js
95
96
  - app/assets/javascripts/yummy_guide_administrate/filter_controls.js
96
97
  - app/assets/javascripts/yummy_guide_administrate/filter_form.js
@@ -98,6 +99,7 @@ files:
98
99
  - app/assets/javascripts/yummy_guide_administrate/resizable_navigation.js
99
100
  - app/assets/javascripts/yummy_guide_administrate/sticky_left_columns.js
100
101
  - app/assets/javascripts/yummy_guide_administrate/sticky_table_headers.js
102
+ - app/assets/stylesheets/yummy_guide_administrate/_column_resizer.scss
101
103
  - app/assets/stylesheets/yummy_guide_administrate/_datetime_input.scss
102
104
  - app/assets/stylesheets/yummy_guide_administrate/_fixed_submit_actions.scss
103
105
  - app/assets/stylesheets/yummy_guide_administrate/_resizable_navigation.scss