yummy-guide-generic-administrate 0.8.8 → 0.8.9

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: 1ed6d8460a0a0e1b9a84b4b69a0e0dfd01195b505b2d9efa984cddf182efea28
4
+ data.tar.gz: d1d8de5996db5b20c7bbf8d5f822d9f62f900b59c1c6818a9104ac2a876961f1
5
5
  SHA512:
6
- metadata.gz: 410dbc0171c5cfc6c0b83c12e7943d58a959707d214c13b570f83973941214394741447654ea658df789c3c3b3f20c097be41fdc8d1fa0f8201ecf723f796f8a
7
- data.tar.gz: 1f750eec8e2b1561d5dde53484be393dfa9194c0af204c6dc251ae27dd6b7a605880de00fae2db9d8e51dfe7901ba8b55d89a28f50fe5296e7055f513695cd6e
6
+ metadata.gz: 0b4f2c4caeb356f6758c40a5dbbcd7278d1303cc46970273d3ff3549c410bfba1b16ec163a2debb87649fb0e29c930401847aa73d7db866373c35125bc4e2f95
7
+ data.tar.gz: 077c2bf515cd0f2b0f46dbf16917eb5dbc781add60f5502b7c027fcfc97867c94ee215eaf5cd54049f4f0d597a1c5f68a7855a11280e54e92106b37a7c3cea84
@@ -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
  }
@@ -422,6 +598,7 @@
422
598
  if (key && storageKeyForTable(table) !== key) return;
423
599
 
424
600
  applyTableColumnWidth(table, columnId, width);
601
+ refreshStickyHeaderLayoutForTable(table);
425
602
  });
426
603
  }
427
604
 
@@ -430,6 +607,7 @@
430
607
  if (key && storageKeyForTable(table) !== key) return;
431
608
 
432
609
  clearTableColumnWidth(table, columnId);
610
+ refreshStickyHeaderLayoutForTable(table);
433
611
  });
434
612
  }
435
613
 
@@ -440,6 +618,8 @@
440
618
 
441
619
  applyTableColumnWidth(table, columnId, width);
442
620
  });
621
+
622
+ refreshStickyHeaderLayoutForTable(table);
443
623
  }
444
624
 
445
625
  function applyStoredWidthsToTables(tables) {
@@ -609,6 +789,8 @@
609
789
  }
610
790
 
611
791
  function refreshStickyLeftColumns(sourceTable) {
792
+ refreshStickyHeaderLayoutForTable(sourceTable);
793
+
612
794
  var api = window.YummyGuideAdministrateStickyLeftColumns;
613
795
 
614
796
  if (api && typeof api.refreshTable === 'function') {
@@ -671,6 +853,7 @@
671
853
  widths[pendingWidth.columnId] = preciseNumber(pendingWidth.width);
672
854
  safeWriteWidths(pendingWidth.storageKey, widths);
673
855
  refreshStickyLeftColumnsForWidth(pendingWidth);
856
+ refreshStickyHeaderLayoutForTable(pendingWidth.sourceTable);
674
857
  window.requestAnimationFrame(function() {
675
858
  stopApplyingWidth(pendingWidth.preview);
676
859
  });
@@ -841,6 +1024,7 @@
841
1024
  });
842
1025
 
843
1026
  applyStoredWidthsToTables(configuredTables);
1027
+ refreshStickyHeaderLayout(root);
844
1028
  }
845
1029
 
846
1030
  function initializeFromDocument() {
@@ -864,7 +1048,12 @@
864
1048
  if (!shouldInitializeForAddedNode(node)) return;
865
1049
 
866
1050
  initializeColumnResizer(node);
1051
+ refreshStickyHeaderLayout(node);
867
1052
  });
1053
+
1054
+ if (mutation.target && mutation.target.nodeType === Node.ELEMENT_NODE) {
1055
+ refreshStickyHeaderLayout(mutation.target);
1056
+ }
868
1057
  });
869
1058
  });
870
1059
 
@@ -874,6 +1063,10 @@
874
1063
  });
875
1064
  }
876
1065
 
1066
+ window.YummyGuideAdministrateColumnResizer = {
1067
+ refreshStickyHeaderLayout: refreshStickyHeaderLayout
1068
+ };
1069
+
877
1070
  setTimeout(initializeFromDocument, 100);
878
1071
  setTimeout(initializeFromDocument, 300);
879
1072
  })();
@@ -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.9"
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")
@@ -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.9
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