yummy-guide-generic-administrate 0.8.8 → 0.8.10

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: 74f5b683cf773df946e0d0e56e1abd66d931e9e5c660afa77c30b80472ae23c6
4
- data.tar.gz: e3a282a8a3cfdedf00d02a5071ce9ae325ea164e905931e14ebcdc7f8bf1aad5
3
+ metadata.gz: 8c30d8690fff8a7358f89a9e8e56a651b9b7630e3d1e69237325a215e0e7a3f0
4
+ data.tar.gz: 488e2ad28e15eec00b0e8387734c1cdf5bd56426ba9f916fe611c03a700203aa
5
5
  SHA512:
6
- metadata.gz: 410dbc0171c5cfc6c0b83c12e7943d58a959707d214c13b570f83973941214394741447654ea658df789c3c3b3f20c097be41fdc8d1fa0f8201ecf723f796f8a
7
- data.tar.gz: 1f750eec8e2b1561d5dde53484be393dfa9194c0af204c6dc251ae27dd6b7a605880de00fae2db9d8e51dfe7901ba8b55d89a28f50fe5296e7055f513695cd6e
6
+ metadata.gz: d95722f019bf3930435bd7602d9d937cf744bfa3b261fa771412f17f6b0ca047959bd3212af6cdd9b0c81992872f019ced8d79671e6fdbe38cb093084f227b78
7
+ data.tar.gz: a0afec4d0c4f2c8615664eea22ca12ef10e5332a70062cde6f535e4a4b251df567c0950e5056a5ea209cd3d3393e73f8b7f2f834056a78b1177ce38744f220a5
@@ -1,5 +1,9 @@
1
1
  (function() {
2
2
  var TABLE_SELECTOR = 'table[data-fixed-columns-count]';
3
+ var CSS_STICKY_TABLE_SELECTOR = '[data-css-sticky-table]';
4
+ var MAIN_CONTENT_SELECTOR = '.main-content';
5
+ var STICKY_PAGE_HEADER_SELECTOR = '.main-content__header--sticky-table-layout, [data-reservations-sticky-header], .main-content__header';
6
+ var STICKY_PAGE_HEADER_HEIGHT_VARIABLE = '--admin-sticky-page-header-height';
3
7
  var HANDLE_CLASS = 'admin-column-resizer__handle';
4
8
  var HEADER_CLASS = 'admin-column-resizer__header';
5
9
  var TABLE_CLASS = 'admin-column-resizer__table';
@@ -19,6 +23,8 @@
19
23
  var generatedRuleCount = 0;
20
24
  var initializedHandles = new WeakSet();
21
25
  var tableStates = new WeakMap();
26
+ var stickyPageHeaderResizeObservers = new WeakMap();
27
+ var stickyPageHeaderLayoutFrames = new WeakMap();
22
28
  var applyingWidth = false;
23
29
 
24
30
  function storageScopeForTable(table) {
@@ -78,6 +84,176 @@
78
84
  return preciseNumber(rectWidth || element.offsetWidth || 0);
79
85
  }
80
86
 
87
+ function pushUnique(array, item) {
88
+ if (item && array.indexOf(item) === -1) {
89
+ array.push(item);
90
+ }
91
+ }
92
+
93
+ function hasCssStickyTable(element) {
94
+ if (!element) return false;
95
+
96
+ return (element.matches && element.matches(CSS_STICKY_TABLE_SELECTOR)) ||
97
+ !!(element.querySelector && element.querySelector(CSS_STICKY_TABLE_SELECTOR));
98
+ }
99
+
100
+ function closestMainContent(element) {
101
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) return null;
102
+
103
+ if (element.matches && element.matches(MAIN_CONTENT_SELECTOR)) {
104
+ return element;
105
+ }
106
+
107
+ return element.closest ? element.closest(MAIN_CONTENT_SELECTOR) : null;
108
+ }
109
+
110
+ function collectStickyTableMainContents(root) {
111
+ var mainContents = [];
112
+ var element = root && root.nodeType === Node.ELEMENT_NODE ? root : null;
113
+
114
+ if (element) {
115
+ pushUnique(mainContents, closestMainContent(element));
116
+
117
+ if (element.matches && element.matches(CSS_STICKY_TABLE_SELECTOR)) {
118
+ pushUnique(mainContents, closestMainContent(element));
119
+ }
120
+ }
121
+
122
+ if (root && root.querySelectorAll) {
123
+ root.querySelectorAll(MAIN_CONTENT_SELECTOR).forEach(function(mainContent) {
124
+ if (hasCssStickyTable(mainContent)) {
125
+ pushUnique(mainContents, mainContent);
126
+ }
127
+ });
128
+
129
+ root.querySelectorAll(CSS_STICKY_TABLE_SELECTOR).forEach(function(tableWrapper) {
130
+ pushUnique(mainContents, closestMainContent(tableWrapper));
131
+ });
132
+ }
133
+
134
+ return mainContents;
135
+ }
136
+
137
+ function stickyPageHeader(mainContent) {
138
+ if (!mainContent) return null;
139
+
140
+ return Array.from(mainContent.children).find(function(child) {
141
+ return child.matches && child.matches(STICKY_PAGE_HEADER_SELECTOR);
142
+ }) || null;
143
+ }
144
+
145
+ function measuredStickyPageHeaderHeight(mainContent, header) {
146
+ var previousHeight = mainContent.style.getPropertyValue(STICKY_PAGE_HEADER_HEIGHT_VARIABLE);
147
+
148
+ if (previousHeight) {
149
+ mainContent.style.removeProperty(STICKY_PAGE_HEADER_HEIGHT_VARIABLE);
150
+ }
151
+
152
+ var rectHeight = header.getBoundingClientRect().height;
153
+ var scrollHeight = header.scrollHeight || 0;
154
+ var height = Math.max(rectHeight || 0, scrollHeight);
155
+
156
+ if (previousHeight) {
157
+ mainContent.style.setProperty(STICKY_PAGE_HEADER_HEIGHT_VARIABLE, previousHeight);
158
+ }
159
+
160
+ return preciseNumber(height);
161
+ }
162
+
163
+ function refreshStickyPageHeaderLayout(mainContent) {
164
+ if (!mainContent) return false;
165
+
166
+ if (!hasCssStickyTable(mainContent)) {
167
+ mainContent.style.removeProperty(STICKY_PAGE_HEADER_HEIGHT_VARIABLE);
168
+ return false;
169
+ }
170
+
171
+ var header = stickyPageHeader(mainContent);
172
+
173
+ if (!header) {
174
+ mainContent.style.removeProperty(STICKY_PAGE_HEADER_HEIGHT_VARIABLE);
175
+ return false;
176
+ }
177
+
178
+ var height = measuredStickyPageHeaderHeight(mainContent, header);
179
+
180
+ if (height > 0) {
181
+ var value = cssPixelValue(height);
182
+
183
+ if (mainContent.style.getPropertyValue(STICKY_PAGE_HEADER_HEIGHT_VARIABLE) !== value) {
184
+ mainContent.style.setProperty(STICKY_PAGE_HEADER_HEIGHT_VARIABLE, value);
185
+ }
186
+ } else {
187
+ mainContent.style.removeProperty(STICKY_PAGE_HEADER_HEIGHT_VARIABLE);
188
+ }
189
+
190
+ return true;
191
+ }
192
+
193
+ function observeStickyPageHeaderLayout(mainContent) {
194
+ if (!window.ResizeObserver || !mainContent) return;
195
+
196
+ var header = stickyPageHeader(mainContent);
197
+ var currentObserver = stickyPageHeaderResizeObservers.get(mainContent);
198
+
199
+ if (!header) {
200
+ if (currentObserver) {
201
+ currentObserver.observer.disconnect();
202
+ stickyPageHeaderResizeObservers.delete(mainContent);
203
+ }
204
+ return;
205
+ }
206
+
207
+ if (currentObserver && currentObserver.header === header) return;
208
+
209
+ if (currentObserver) {
210
+ currentObserver.observer.disconnect();
211
+ }
212
+
213
+ var observer = new ResizeObserver(function() {
214
+ scheduleStickyPageHeaderLayout(mainContent);
215
+ });
216
+
217
+ observer.observe(header);
218
+
219
+ stickyPageHeaderResizeObservers.set(mainContent, {
220
+ header: header,
221
+ observer: observer
222
+ });
223
+ }
224
+
225
+ function scheduleStickyPageHeaderLayout(mainContent) {
226
+ if (!mainContent || stickyPageHeaderLayoutFrames.has(mainContent)) return;
227
+
228
+ var frame = window.requestAnimationFrame(function() {
229
+ stickyPageHeaderLayoutFrames.delete(mainContent);
230
+ refreshStickyPageHeaderLayout(mainContent);
231
+ observeStickyPageHeaderLayout(mainContent);
232
+ });
233
+
234
+ stickyPageHeaderLayoutFrames.set(mainContent, frame);
235
+ }
236
+
237
+ function refreshStickyHeaderLayout(root) {
238
+ var refreshed = false;
239
+
240
+ collectStickyTableMainContents(root || document).forEach(function(mainContent) {
241
+ refreshed = refreshStickyPageHeaderLayout(mainContent) || refreshed;
242
+ observeStickyPageHeaderLayout(mainContent);
243
+ });
244
+
245
+ return refreshed;
246
+ }
247
+
248
+ function refreshStickyHeaderLayoutForTable(table) {
249
+ var mainContent = closestMainContent(table);
250
+
251
+ if (!mainContent) return false;
252
+
253
+ scheduleStickyPageHeaderLayout(mainContent);
254
+ return true;
255
+ }
256
+
81
257
  function viewportHeight() {
82
258
  return window.innerHeight || document.documentElement.clientHeight || 0;
83
259
  }
@@ -218,14 +394,6 @@
218
394
  return tables;
219
395
  }
220
396
 
221
- function shouldInitializeForAddedNode(node) {
222
- if (!node.matches) return false;
223
- if (node.matches(TABLE_SELECTOR) || (node.querySelector && node.querySelector(TABLE_SELECTOR))) return true;
224
- if (!node.closest || !node.closest(TABLE_SELECTOR)) return false;
225
-
226
- return !!node.closest('thead') || node.matches('colgroup, col');
227
- }
228
-
229
397
  function allTrackedTables() {
230
398
  return Array.from(document.querySelectorAll(TABLE_SELECTOR));
231
399
  }
@@ -422,6 +590,7 @@
422
590
  if (key && storageKeyForTable(table) !== key) return;
423
591
 
424
592
  applyTableColumnWidth(table, columnId, width);
593
+ refreshStickyHeaderLayoutForTable(table);
425
594
  });
426
595
  }
427
596
 
@@ -430,6 +599,7 @@
430
599
  if (key && storageKeyForTable(table) !== key) return;
431
600
 
432
601
  clearTableColumnWidth(table, columnId);
602
+ refreshStickyHeaderLayoutForTable(table);
433
603
  });
434
604
  }
435
605
 
@@ -440,6 +610,8 @@
440
610
 
441
611
  applyTableColumnWidth(table, columnId, width);
442
612
  });
613
+
614
+ refreshStickyHeaderLayoutForTable(table);
443
615
  }
444
616
 
445
617
  function applyStoredWidthsToTables(tables) {
@@ -609,6 +781,8 @@
609
781
  }
610
782
 
611
783
  function refreshStickyLeftColumns(sourceTable) {
784
+ refreshStickyHeaderLayoutForTable(sourceTable);
785
+
612
786
  var api = window.YummyGuideAdministrateStickyLeftColumns;
613
787
 
614
788
  if (api && typeof api.refreshTable === 'function') {
@@ -671,6 +845,7 @@
671
845
  widths[pendingWidth.columnId] = preciseNumber(pendingWidth.width);
672
846
  safeWriteWidths(pendingWidth.storageKey, widths);
673
847
  refreshStickyLeftColumnsForWidth(pendingWidth);
848
+ refreshStickyHeaderLayoutForTable(pendingWidth.sourceTable);
674
849
  window.requestAnimationFrame(function() {
675
850
  stopApplyingWidth(pendingWidth.preview);
676
851
  });
@@ -841,6 +1016,7 @@
841
1016
  });
842
1017
 
843
1018
  applyStoredWidthsToTables(configuredTables);
1019
+ refreshStickyHeaderLayout(root);
844
1020
  }
845
1021
 
846
1022
  function initializeFromDocument() {
@@ -856,23 +1032,9 @@
856
1032
  document.addEventListener('turbo:load', initializeFromDocument);
857
1033
  window.addEventListener('resize', initializeFromDocument);
858
1034
 
859
- if (window.MutationObserver) {
860
- var mutationObserver = new MutationObserver(function(mutations) {
861
- mutations.forEach(function(mutation) {
862
- mutation.addedNodes.forEach(function(node) {
863
- if (node.nodeType !== Node.ELEMENT_NODE) return;
864
- if (!shouldInitializeForAddedNode(node)) return;
865
-
866
- initializeColumnResizer(node);
867
- });
868
- });
869
- });
870
-
871
- mutationObserver.observe(document.documentElement, {
872
- childList: true,
873
- subtree: true
874
- });
875
- }
1035
+ window.YummyGuideAdministrateColumnResizer = {
1036
+ refreshStickyHeaderLayout: refreshStickyHeaderLayout
1037
+ };
876
1038
 
877
1039
  setTimeout(initializeFromDocument, 100);
878
1040
  setTimeout(initializeFromDocument, 300);
@@ -419,11 +419,6 @@
419
419
  cursor: default;
420
420
  }
421
421
 
422
- .admin-copy-cell__button[data-behavior="copy-cell"],
423
- .admin-copy-cell__icon {
424
- display: none !important;
425
- }
426
-
427
422
  .admin-copy-cell__link:hover,
428
423
  .admin-copy-cell__link:focus-visible,
429
424
  .admin-copy-cell__link:active,
@@ -2,6 +2,6 @@
2
2
 
3
3
  module YummyGuide
4
4
  module Administrate
5
- VERSION = "0.8.8"
5
+ VERSION = "0.8.10"
6
6
  end
7
7
  end
@@ -270,6 +270,7 @@ RSpec.describe YummyGuide::Administrate::CollectionHelper do
270
270
  expect(cell[:linkable]).to be(false)
271
271
  expect(cell[:content]).to include('href="/areas/japan/nagano/matsumoto/articles/test-article"')
272
272
  expect(cell[:content]).to include('data-behavior="copy-cell"')
273
+ expect(cell[:content]).to include('class="admin-copy-cell__icon"')
273
274
  expect(cell[:content]).to include('data-copy-text="Page"')
274
275
  end
275
276
 
@@ -42,7 +42,6 @@ RSpec.describe "column resizer assets" do
42
42
  # 固定ヘッダー複製用の同期処理を持たず、元テーブルの固定左列だけ再計算することを静的に確認する
43
43
  it "does not depend on duplicated fixed header synchronization" do
44
44
  expect(javascript_source).not_to include("YummyGuideAdministrateStickyTableHeaders")
45
- expect(javascript_source).not_to include("refreshStickyHeader")
46
45
  expect(javascript_source).not_to include("scheduleStickyRefresh")
47
46
  expect(javascript_source).not_to include("table-fixed-header__table")
48
47
  expect(javascript_source).to include("refreshStickyLeftColumnsForWidth(pendingWidth)")
@@ -147,7 +146,7 @@ RSpec.describe "column resizer assets" do
147
146
  expect(components_source).to include("position: static !important")
148
147
  expect(components_source).to include('table[data-mobile-fixed-columns-count="1"] th.sticky-left-mobile')
149
148
  expect(components_source).to include("position: sticky !important")
150
- expect(components_source).to include("min-inline-size: 100vw !important")
149
+ expect(components_source).to include("min-inline-size: 0 !important")
151
150
  expect(components_source).to include("width: max-content !important")
152
151
  expect(components_source).to include("@media (max-width: 767px)")
153
152
  expect(components_source).to include(".scroll-table table th.sticky.actions-column")
@@ -166,6 +165,21 @@ RSpec.describe "column resizer assets" do
166
165
  expect(components_source).to include("width: var(--admin-sticky-actions-right, 0px)")
167
166
  end
168
167
 
168
+ # 固定ページヘッダーの実高さをCSS変数へ反映し、テーブルヘッダー位置を追従させることを静的に確認する
169
+ it "updates sticky table header offset from measured page header height" do
170
+ expect(javascript_source).to include("CSS_STICKY_TABLE_SELECTOR = '[data-css-sticky-table]'")
171
+ expect(javascript_source).to include("STICKY_PAGE_HEADER_HEIGHT_VARIABLE = '--admin-sticky-page-header-height'")
172
+ expect(javascript_source).to include("function measuredStickyPageHeaderHeight(mainContent, header)")
173
+ expect(javascript_source).to include("header.getBoundingClientRect().height")
174
+ expect(javascript_source).to include("header.scrollHeight || 0")
175
+ expect(javascript_source).to include("mainContent.style.setProperty(STICKY_PAGE_HEADER_HEIGHT_VARIABLE, value)")
176
+ expect(javascript_source).to include("mainContent.style.removeProperty(STICKY_PAGE_HEADER_HEIGHT_VARIABLE)")
177
+ expect(javascript_source).to include("new ResizeObserver(function()")
178
+ expect(javascript_source).to include("refreshStickyHeaderLayoutForTable(table)")
179
+ expect(javascript_source).to include("refreshStickyHeaderLayoutForTable(pendingWidth.sourceTable)")
180
+ expect(javascript_source).to include("refreshStickyHeaderLayout: refreshStickyHeaderLayout")
181
+ end
182
+
169
183
  # 固定ナビがテーブルより前面の不透明なスクロール領域として表示されることを静的に確認する
170
184
  it "keeps fixed navigation above sticky tables with an opaque background" do
171
185
  expect(components_source).to include("--admin-page-background: #f6f7f7")
@@ -216,6 +230,14 @@ RSpec.describe "column resizer assets" do
216
230
  expect(components_source).not_to include("table-with-fixed-header")
217
231
  end
218
232
 
233
+ # document全体のDOM変更監視と一時切り分け用パラメータが残っていないことを静的に確認する
234
+ it "does not observe document mutations during table initialization" do
235
+ expect(javascript_source).not_to include("new MutationObserver")
236
+ expect(javascript_source).not_to include("mutationObserver.observe(document.documentElement")
237
+ expect(javascript_source).not_to include("admin_column_resizer_disable")
238
+ expect(javascript_source).not_to include("probeFeatureDisabled")
239
+ end
240
+
219
241
  # 幅リセット時も固定列のCSS変数を再計算することを静的に確認する
220
242
  it "recalculates CSS sticky-left offsets after clearing a column width" do
221
243
  expect(javascript_source).to include("clearColumnWidth(columnId, key)")
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "copy cell assets" do
4
+ let(:components_source) do
5
+ File.read(File.expand_path("../../../app/assets/stylesheets/yummy_guide_administrate/components.scss", __dir__))
6
+ end
7
+
8
+ # コピー用ボタンとアイコンをCSSで強制的に隠さないことを静的に確認する
9
+ it "does not force-hide the copy cell button or icon" do
10
+ expect(components_source.scan(/\.admin-copy-cell__(?:button|icon)[^{]*\{[^}]*display:\s*none\s*!important/m)).to be_empty
11
+ expect(components_source).to include(".admin-copy-cell__icon {\n display: inline-flex;")
12
+ expect(components_source).to include('mask: image-url("yummy_guide_administrate/icon-copy.svg") center / contain no-repeat;')
13
+ end
14
+
15
+ # テーブルセルではホバーやフォーカス時にコピー操作が表示されることを静的に確認する
16
+ it "reveals copy cell controls on hover or focus" do
17
+ expect(components_source).to include("td.cell-data:hover .admin-copy-cell__button:not([disabled])")
18
+ expect(components_source).to include(".admin-copy-cell:focus-within .admin-copy-cell__link")
19
+ expect(components_source).to include(".admin-copy-cell__button:focus-visible")
20
+ expect(components_source).to include("opacity: 1;")
21
+ end
22
+
23
+ # 詳細画面ではコピー操作が常時表示される指定を維持することを静的に確認する
24
+ it "keeps attribute copy controls visible" do
25
+ expect(components_source).to match(/\.attribute-data \.admin-copy-cell__link,\s*\.attribute-data \.admin-copy-cell__button:not\(\[disabled\]\) \{\s*opacity: 1;/m)
26
+ end
27
+ 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.8
4
+ version: 0.8.10
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-15 00:00:00.000000000 Z
11
+ date: 2026-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: administrate
@@ -144,6 +144,7 @@ files:
144
144
  - spec/yummy_guide/administrate/application_dashboard_spec.rb
145
145
  - spec/yummy_guide/administrate/collection_helper_spec.rb
146
146
  - spec/yummy_guide/administrate/column_resizer_asset_spec.rb
147
+ - spec/yummy_guide/administrate/copy_cell_asset_spec.rb
147
148
  - spec/yummy_guide/administrate/datetime_filter_parameters_spec.rb
148
149
  - spec/yummy_guide/administrate/datetime_input_helper_spec.rb
149
150
  - spec/yummy_guide/administrate/fields/json_pretty_field_spec.rb