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 +4 -4
- data/README.md +4 -1
- data/app/assets/javascripts/yummy_guide_administrate/column_resizer.js +610 -0
- data/app/assets/stylesheets/yummy_guide_administrate/_column_resizer.scss +64 -0
- data/app/assets/stylesheets/yummy_guide_administrate/_resizable_navigation.scss +10 -6
- data/app/assets/stylesheets/yummy_guide_administrate/components.scss +1 -0
- data/app/helpers/yummy_guide/administrate/collection_helper.rb +11 -0
- data/app/views/yummy_guide/administrate/administrate/application/_collection.html.erb +5 -0
- data/lib/yummy_guide/administrate/engine.rb +1 -0
- data/lib/yummy_guide/administrate/version.rb +1 -1
- data/spec/yummy_guide/administrate/collection_helper_spec.rb +23 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b23f3b7fd7d17420fdeaf7325f51f1f70b93fb86497bcdb6621b617b5086d926
|
|
4
|
+
data.tar.gz: 38004b14e38a03b882d6e84253fc63ad1a2e8480ab287ee27ced1d13e81239a7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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:
|
|
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: #
|
|
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
|
}
|
|
@@ -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
|
|
@@ -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.
|
|
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-
|
|
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
|