plutonium 0.49.1 → 0.50.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-definition/SKILL.md +87 -2
  3. data/.claude/skills/plutonium-installation/SKILL.md +6 -0
  4. data/.claude/skills/plutonium-views/SKILL.md +59 -0
  5. data/CHANGELOG.md +12 -0
  6. data/app/assets/plutonium.css +2 -2
  7. data/app/assets/plutonium.js +369 -25
  8. data/app/assets/plutonium.js.map +4 -4
  9. data/app/assets/plutonium.min.js +45 -45
  10. data/app/assets/plutonium.min.js.map +4 -4
  11. data/app/views/plutonium/_resource_header.html.erb +4 -4
  12. data/app/views/plutonium/_resource_sidebar.html.erb +9 -9
  13. data/app/views/resource/_resource_grid.html.erb +1 -0
  14. data/config/brakeman.ignore +25 -2
  15. data/docs/reference/definition/actions.md +14 -1
  16. data/docs/reference/definition/index.md +58 -0
  17. data/docs/reference/views/index.md +43 -0
  18. data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md +841 -0
  19. data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md.tasks.json +103 -0
  20. data/docs/superpowers/specs/2026-05-07-ui-layout-overhaul-design.md +270 -0
  21. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  22. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +1 -0
  23. data/lib/generators/pu/core/update/update_generator.rb +20 -0
  24. data/lib/generators/pu/lite/rails_pulse/rails_pulse_generator.rb +54 -5
  25. data/lib/plutonium/action/base.rb +44 -1
  26. data/lib/plutonium/action/interactive.rb +1 -1
  27. data/lib/plutonium/configuration.rb +4 -0
  28. data/lib/plutonium/definition/actions.rb +3 -0
  29. data/lib/plutonium/definition/base.rb +8 -0
  30. data/lib/plutonium/definition/metadata.rb +40 -0
  31. data/lib/plutonium/definition/views.rb +94 -0
  32. data/lib/plutonium/helpers/turbo_helper.rb +1 -1
  33. data/lib/plutonium/interaction/response/redirect.rb +1 -1
  34. data/lib/plutonium/query/base.rb +8 -0
  35. data/lib/plutonium/query/filters/association.rb +30 -8
  36. data/lib/plutonium/query/filters/boolean.rb +5 -0
  37. data/lib/plutonium/resource/controllers/presentable.rb +11 -2
  38. data/lib/plutonium/resource/definition.rb +42 -0
  39. data/lib/plutonium/resource/query_object.rb +64 -6
  40. data/lib/plutonium/testing/resource_definition.rb +2 -2
  41. data/lib/plutonium/ui/action_button.rb +4 -2
  42. data/lib/plutonium/ui/component/kit.rb +12 -0
  43. data/lib/plutonium/ui/display/base.rb +3 -1
  44. data/lib/plutonium/ui/display/resource.rb +109 -25
  45. data/lib/plutonium/ui/display/theme.rb +2 -1
  46. data/lib/plutonium/ui/dyna_frame/content.rb +8 -14
  47. data/lib/plutonium/ui/empty_card.rb +1 -1
  48. data/lib/plutonium/ui/form/base.rb +29 -1
  49. data/lib/plutonium/ui/form/components/hidden_wrapper.rb +25 -0
  50. data/lib/plutonium/ui/form/components/resource_select.rb +79 -1
  51. data/lib/plutonium/ui/form/components/secure_association.rb +7 -2
  52. data/lib/plutonium/ui/form/components/sticky_footer.rb +17 -0
  53. data/lib/plutonium/ui/form/resource.rb +48 -9
  54. data/lib/plutonium/ui/form/theme.rb +1 -1
  55. data/lib/plutonium/ui/frame_navigator_panel.rb +7 -4
  56. data/lib/plutonium/ui/grid/card.rb +235 -0
  57. data/lib/plutonium/ui/grid/resource.rb +149 -0
  58. data/lib/plutonium/ui/layout/base.rb +37 -1
  59. data/lib/plutonium/ui/layout/header.rb +1 -2
  60. data/lib/plutonium/ui/layout/icon_rail.rb +212 -0
  61. data/lib/plutonium/ui/layout/resource_layout.rb +10 -3
  62. data/lib/plutonium/ui/layout/sidebar.rb +12 -24
  63. data/lib/plutonium/ui/layout/topbar.rb +100 -0
  64. data/lib/plutonium/ui/modal/base.rb +109 -0
  65. data/lib/plutonium/ui/modal/centered.rb +21 -0
  66. data/lib/plutonium/ui/modal/slideover.rb +26 -0
  67. data/lib/plutonium/ui/page/base.rb +25 -6
  68. data/lib/plutonium/ui/page/edit.rb +13 -1
  69. data/lib/plutonium/ui/page/index.rb +40 -1
  70. data/lib/plutonium/ui/page/interactive_action.rb +8 -39
  71. data/lib/plutonium/ui/page/new.rb +13 -1
  72. data/lib/plutonium/ui/page/show.rb +8 -1
  73. data/lib/plutonium/ui/page_header.rb +8 -13
  74. data/lib/plutonium/ui/panel.rb +10 -19
  75. data/lib/plutonium/ui/sidebar_menu.rb +2 -25
  76. data/lib/plutonium/ui/tab_list.rb +29 -7
  77. data/lib/plutonium/ui/table/base.rb +106 -0
  78. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +12 -4
  79. data/lib/plutonium/ui/table/components/filter_form.rb +171 -0
  80. data/lib/plutonium/ui/table/components/filter_pills.rb +89 -0
  81. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +13 -12
  82. data/lib/plutonium/ui/table/components/scopes_pills.rb +67 -0
  83. data/lib/plutonium/ui/table/components/selection_column.rb +2 -11
  84. data/lib/plutonium/ui/table/components/toolbar.rb +104 -0
  85. data/lib/plutonium/ui/table/components/view_switcher.rb +81 -0
  86. data/lib/plutonium/ui/table/resource.rb +158 -89
  87. data/lib/plutonium/ui/table/theme.rb +14 -5
  88. data/lib/plutonium/version.rb +1 -1
  89. data/lib/plutonium.rb +6 -0
  90. data/package.json +1 -1
  91. data/src/css/components.css +304 -131
  92. data/src/css/tokens.css +101 -85
  93. data/src/js/controllers/autosubmit_controller.js +24 -0
  94. data/src/js/controllers/bulk_actions_controller.js +15 -16
  95. data/src/js/controllers/capture_url_controller.js +14 -0
  96. data/src/js/controllers/filter_panel_controller.js +77 -19
  97. data/src/js/controllers/frame_navigator_controller.js +34 -6
  98. data/src/js/controllers/icon_rail_controller.js +22 -0
  99. data/src/js/controllers/icon_rail_flyout_controller.js +128 -0
  100. data/src/js/controllers/register_controllers.js +16 -0
  101. data/src/js/controllers/resource_tab_list_controller.js +56 -3
  102. data/src/js/controllers/row_click_controller.js +21 -0
  103. data/src/js/controllers/table_column_menu_controller.js +43 -0
  104. data/src/js/controllers/table_header_controller.js +16 -0
  105. data/src/js/controllers/view_switcher_controller.js +29 -0
  106. metadata +31 -3
@@ -13326,6 +13326,12 @@
13326
13326
  this.frameTarget.removeEventListener("turbo:fetch-request-error", this.frameFailed);
13327
13327
  }
13328
13328
  frameLoading(event) {
13329
+ if (event) {
13330
+ const trigger = event.target.closest("a, form");
13331
+ const requested = trigger?.dataset?.turboFrame;
13332
+ if (requested && requested !== this.frameTarget.id)
13333
+ return;
13334
+ }
13329
13335
  this.#loadingStarted();
13330
13336
  }
13331
13337
  frameFailed(event) {
@@ -13347,17 +13353,26 @@
13347
13353
  }
13348
13354
  homeButtonClicked(event) {
13349
13355
  this.frameLoading(null);
13356
+ this.srcHistory = [this.originalFrameSrc];
13357
+ this.#updateNavigationButtonsDisplay();
13358
+ this._homeRequested = true;
13350
13359
  this.frameTarget.src = this.originalFrameSrc;
13360
+ this.frameTarget.reload();
13351
13361
  }
13352
13362
  get currentSrc() {
13353
13363
  return this.srcHistory[this.srcHistory.length - 1];
13354
13364
  }
13355
13365
  #notifySrcChanged(src) {
13356
- if (src == this.currentSrc) {
13357
- } else if (src == this.originalFrameSrc)
13366
+ if (this._homeRequested) {
13367
+ this._homeRequested = false;
13358
13368
  this.srcHistory = [src];
13359
- else
13369
+ this.originalFrameSrc = src;
13370
+ } else if (src == this.currentSrc) {
13371
+ } else if (src == this.originalFrameSrc) {
13372
+ this.srcHistory = [src];
13373
+ } else {
13360
13374
  this.srcHistory.push(src);
13375
+ }
13361
13376
  this.#updateNavigationButtonsDisplay();
13362
13377
  if (this.hasMaximizeLinkTarget)
13363
13378
  this.maximizeLinkTarget.href = src;
@@ -16974,12 +16989,28 @@ ${text2}</tr>
16974
16989
  connect() {
16975
16990
  this.activeClasses = this.hasActiveClassesValue ? this.activeClassesValue.split(" ") : [];
16976
16991
  this.inActiveClasses = this.hasInActiveClassesValue ? this.inActiveClassesValue.split(" ") : [];
16977
- this.#selectInternal(this.defaultTabValue || this.btnTargets[0].id);
16992
+ const fromHash = this.#buttonIdFromHash();
16993
+ const initialId = fromHash || this.defaultTabValue || this.btnTargets[0]?.id;
16994
+ this.#selectInternal(initialId, { skipFocus: true, skipHashUpdate: true });
16995
+ this._syncFromHash = this._syncFromHash.bind(this);
16996
+ window.addEventListener("hashchange", this._syncFromHash);
16997
+ document.addEventListener("turbo:load", this._syncFromHash);
16998
+ }
16999
+ disconnect() {
17000
+ if (this._syncFromHash) {
17001
+ window.removeEventListener("hashchange", this._syncFromHash);
17002
+ document.removeEventListener("turbo:load", this._syncFromHash);
17003
+ }
17004
+ }
17005
+ _syncFromHash() {
17006
+ const id2 = this.#buttonIdFromHash();
17007
+ if (id2)
17008
+ this.#selectInternal(id2, { skipFocus: true, skipHashUpdate: true });
16978
17009
  }
16979
17010
  select(event) {
16980
17011
  this.#selectInternal(event.currentTarget.id);
16981
17012
  }
16982
- #selectInternal(id2) {
17013
+ #selectInternal(id2, options2 = {}) {
16983
17014
  const selectedBtn = this.btnTargets.find((element) => element.id === id2);
16984
17015
  if (!selectedBtn) {
16985
17016
  console.error(`Tab Button with id "${id2}" not found`);
@@ -17006,10 +17037,29 @@ ${text2}</tr>
17006
17037
  selectedBtn.classList.add(...this.activeClasses);
17007
17038
  selectedTab.hidden = false;
17008
17039
  selectedTab.setAttribute("aria-hidden", "false");
17009
- if (selectedBtn !== document.activeElement) {
17040
+ if (!options2.skipHashUpdate)
17041
+ this.#updateHash(id2);
17042
+ if (!options2.skipFocus && selectedBtn !== document.activeElement) {
17010
17043
  selectedBtn.focus();
17011
17044
  }
17012
17045
  }
17046
+ // Button ids follow `${identifier}-tab`. The URL hash carries just
17047
+ // the identifier (e.g., #details, #orders).
17048
+ #buttonIdFromHash() {
17049
+ const hash3 = window.location.hash.replace(/^#/, "");
17050
+ if (!hash3)
17051
+ return null;
17052
+ const candidateId = `${hash3}-tab`;
17053
+ const exists = this.btnTargets.some((btn) => btn.id === candidateId);
17054
+ return exists ? candidateId : null;
17055
+ }
17056
+ #updateHash(buttonId) {
17057
+ const identifier = buttonId.replace(/-tab$/, "");
17058
+ const newHash = `#${identifier}`;
17059
+ if (window.location.hash !== newHash) {
17060
+ history.replaceState(null, "", newHash);
17061
+ }
17062
+ }
17013
17063
  };
17014
17064
 
17015
17065
  // node_modules/@uppy/utils/lib/Translator.js
@@ -27854,18 +27904,7 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
27854
27904
 
27855
27905
  // src/js/controllers/bulk_actions_controller.js
27856
27906
  var bulk_actions_controller_default = class extends Controller {
27857
- static targets = ["checkbox", "checkboxAll", "toolbar", "selectedCount", "actionButton", "selectionCell"];
27858
- static values = {
27859
- hasActions: { type: Boolean, default: false }
27860
- };
27861
- connect() {
27862
- if (this.hasActionsValue) {
27863
- this.enableSelection();
27864
- }
27865
- }
27866
- enableSelection() {
27867
- this.selectionCellTargets.forEach((el) => el.classList.remove("hidden"));
27868
- }
27907
+ static targets = ["checkbox", "checkboxAll", "toolbar", "selectedCount", "actionButton", "filterPills"];
27869
27908
  toggle() {
27870
27909
  this.updateUI();
27871
27910
  }
@@ -27884,6 +27923,9 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
27884
27923
  if (this.hasToolbarTarget) {
27885
27924
  this.toolbarTarget.classList.toggle("hidden", checked.length === 0);
27886
27925
  }
27926
+ if (this.hasFilterPillsTarget) {
27927
+ this.filterPillsTarget.classList.toggle("hidden", checked.length > 0);
27928
+ }
27887
27929
  if (this.hasSelectedCountTarget) {
27888
27930
  this.selectedCountTarget.textContent = checked.length;
27889
27931
  }
@@ -27919,6 +27961,14 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
27919
27961
  const allowedActions = checkbox.dataset.allowedActions;
27920
27962
  return allowedActions ? allowedActions.split(",").filter((a4) => a4) : [];
27921
27963
  }
27964
+ clearSelection() {
27965
+ this.checkboxTargets.forEach((cb) => cb.checked = false);
27966
+ if (this.hasCheckboxAllTarget) {
27967
+ this.checkboxAllTarget.checked = false;
27968
+ this.checkboxAllTarget.indeterminate = false;
27969
+ }
27970
+ this.updateUI();
27971
+ }
27922
27972
  get checked() {
27923
27973
  return this.checkboxTargets.filter((cb) => cb.checked);
27924
27974
  }
@@ -27929,6 +27979,56 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
27929
27979
 
27930
27980
  // src/js/controllers/filter_panel_controller.js
27931
27981
  var filter_panel_controller_default = class extends Controller {
27982
+ static targets = ["panel", "backdrop"];
27983
+ connect() {
27984
+ this._onKeydown = this._onKeydown.bind(this);
27985
+ }
27986
+ disconnect() {
27987
+ if (this.isOpen) {
27988
+ document.removeEventListener("keydown", this._onKeydown);
27989
+ this._unlockBodyScroll();
27990
+ }
27991
+ }
27992
+ toggle() {
27993
+ this.isOpen ? this.close() : this.open();
27994
+ }
27995
+ open() {
27996
+ if (this.hasPanelTarget) {
27997
+ this.panelTarget.setAttribute("data-open", "");
27998
+ this.panelTarget.setAttribute("aria-hidden", "false");
27999
+ }
28000
+ if (this.hasBackdropTarget)
28001
+ this.backdropTarget.setAttribute("data-open", "");
28002
+ this._lockBodyScroll();
28003
+ document.addEventListener("keydown", this._onKeydown);
28004
+ }
28005
+ close() {
28006
+ if (this.hasPanelTarget) {
28007
+ this.panelTarget.removeAttribute("data-open");
28008
+ this.panelTarget.setAttribute("aria-hidden", "true");
28009
+ }
28010
+ if (this.hasBackdropTarget)
28011
+ this.backdropTarget.removeAttribute("data-open");
28012
+ this._unlockBodyScroll();
28013
+ document.removeEventListener("keydown", this._onKeydown);
28014
+ }
28015
+ // Mirrors remote-modal's approach: stash the body's current overflow
28016
+ // and restore it on close. Avoids stomping a value another component
28017
+ // (e.g. an open dialog) may have set.
28018
+ _lockBodyScroll() {
28019
+ if (this._previousBodyOverflow != null)
28020
+ return;
28021
+ this._previousBodyOverflow = document.body.style.overflow;
28022
+ document.body.style.overflow = "hidden";
28023
+ }
28024
+ _unlockBodyScroll() {
28025
+ if (this._previousBodyOverflow == null)
28026
+ return;
28027
+ document.body.style.overflow = this._previousBodyOverflow;
28028
+ this._previousBodyOverflow = null;
28029
+ }
28030
+ // Reset every input under this controller's scope, then submit so the
28031
+ // table reflects the cleared filters immediately.
27932
28032
  clear() {
27933
28033
  this.element.querySelectorAll("input, select, textarea").forEach((input) => {
27934
28034
  if (input.type === "checkbox" || input.type === "radio") {
@@ -27936,23 +28036,27 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
27936
28036
  } else if (input.tagName === "SELECT") {
27937
28037
  input.selectedIndex = 0;
27938
28038
  } else if (input.type === "hidden") {
27939
- if (input.dataset.controller === "flatpickr") {
28039
+ if (input.dataset.controller === "flatpickr")
27940
28040
  input.value = "";
27941
- }
27942
28041
  } else {
27943
28042
  input.value = "";
27944
28043
  }
27945
28044
  });
27946
28045
  this.element.querySelectorAll('[data-controller="flatpickr"]').forEach((input) => {
27947
28046
  const controller = this.application.getControllerForElementAndIdentifier(input, "flatpickr");
27948
- if (controller?.picker) {
28047
+ if (controller?.picker)
27949
28048
  controller.picker.clear();
27950
- }
27951
28049
  });
27952
- const form = this.element.closest("form");
27953
- if (form) {
28050
+ const form = this.element.querySelector("form");
28051
+ if (form)
27954
28052
  form.requestSubmit();
27955
- }
28053
+ }
28054
+ get isOpen() {
28055
+ return this.hasPanelTarget && this.panelTarget.hasAttribute("data-open");
28056
+ }
28057
+ _onKeydown(event) {
28058
+ if (event.key === "Escape")
28059
+ this.close();
27956
28060
  }
27957
28061
  };
27958
28062
 
@@ -28033,6 +28137,238 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28033
28137
  }
28034
28138
  };
28035
28139
 
28140
+ // src/js/controllers/icon_rail_controller.js
28141
+ var icon_rail_controller_default = class extends Controller {
28142
+ static values = {
28143
+ storageKey: { type: String, default: "pu_rail_pinned" }
28144
+ };
28145
+ connect() {
28146
+ const pinned = localStorage.getItem(this.storageKeyValue) === "true";
28147
+ if (pinned) {
28148
+ document.body.classList.add("pu-rail-pinned");
28149
+ }
28150
+ }
28151
+ togglePin() {
28152
+ const pinned = document.body.classList.toggle("pu-rail-pinned");
28153
+ localStorage.setItem(this.storageKeyValue, pinned);
28154
+ }
28155
+ };
28156
+
28157
+ // src/js/controllers/icon_rail_flyout_controller.js
28158
+ var icon_rail_flyout_controller_default = class extends Controller {
28159
+ static targets = ["trigger", "panel"];
28160
+ static values = {
28161
+ closeDelay: { type: Number, default: 150 }
28162
+ };
28163
+ connect() {
28164
+ this._closeTimer = null;
28165
+ this._open = false;
28166
+ this._panel = null;
28167
+ this._panelHome = null;
28168
+ this._onPanelEnter = () => {
28169
+ if (this._closeTimer) {
28170
+ clearTimeout(this._closeTimer);
28171
+ this._closeTimer = null;
28172
+ }
28173
+ };
28174
+ this._onPanelLeave = () => this.scheduleClose();
28175
+ }
28176
+ disconnect() {
28177
+ this._returnPanel();
28178
+ }
28179
+ open() {
28180
+ if (this._closeTimer) {
28181
+ clearTimeout(this._closeTimer);
28182
+ this._closeTimer = null;
28183
+ }
28184
+ if (this._open)
28185
+ return;
28186
+ if (!this._panel && !this.hasPanelTarget)
28187
+ return;
28188
+ this._open = true;
28189
+ this.element.dataset.flyoutOpen = "true";
28190
+ this._portalPanel();
28191
+ this._position();
28192
+ }
28193
+ scheduleClose() {
28194
+ if (this._closeTimer)
28195
+ clearTimeout(this._closeTimer);
28196
+ this._closeTimer = setTimeout(() => this.close(), this.closeDelayValue);
28197
+ }
28198
+ close() {
28199
+ if (!this._open)
28200
+ return;
28201
+ this._open = false;
28202
+ delete this.element.dataset.flyoutOpen;
28203
+ this._returnPanel();
28204
+ }
28205
+ toggle(event) {
28206
+ event.preventDefault();
28207
+ this._open ? this.close() : this.open();
28208
+ }
28209
+ closeOnEsc(event) {
28210
+ if (event.key === "Escape")
28211
+ this.close();
28212
+ }
28213
+ _portalPanel() {
28214
+ if (this._panel)
28215
+ return;
28216
+ const panel = this.panelTarget;
28217
+ if (!panel)
28218
+ return;
28219
+ this._panel = panel;
28220
+ this._panelHome = panel.parentElement;
28221
+ panel.addEventListener("mouseenter", this._onPanelEnter);
28222
+ panel.addEventListener("mouseleave", this._onPanelLeave);
28223
+ document.body.appendChild(panel);
28224
+ panel.style.display = "block";
28225
+ }
28226
+ _returnPanel() {
28227
+ if (!this._panel)
28228
+ return;
28229
+ const panel = this._panel;
28230
+ panel.removeEventListener("mouseenter", this._onPanelEnter);
28231
+ panel.removeEventListener("mouseleave", this._onPanelLeave);
28232
+ panel.style.position = "";
28233
+ panel.style.left = "";
28234
+ panel.style.top = "";
28235
+ panel.style.display = "";
28236
+ if (this._panelHome && document.contains(this._panelHome)) {
28237
+ this._panelHome.appendChild(panel);
28238
+ } else {
28239
+ panel.remove();
28240
+ }
28241
+ this._panel = null;
28242
+ this._panelHome = null;
28243
+ }
28244
+ _position() {
28245
+ if (!this._panel || !this.hasTriggerTarget)
28246
+ return;
28247
+ const panel = this._panel;
28248
+ const triggerRect = this.triggerTarget.getBoundingClientRect();
28249
+ panel.style.position = "fixed";
28250
+ panel.style.left = `${triggerRect.right + 4}px`;
28251
+ panel.style.top = `${triggerRect.top}px`;
28252
+ requestAnimationFrame(() => {
28253
+ const panelRect = panel.getBoundingClientRect();
28254
+ const viewportH = window.innerHeight;
28255
+ if (panelRect.bottom > viewportH - 8) {
28256
+ const overflow = panelRect.bottom - (viewportH - 8);
28257
+ panel.style.top = `${parseFloat(panel.style.top) - overflow}px`;
28258
+ }
28259
+ });
28260
+ }
28261
+ };
28262
+
28263
+ // src/js/controllers/table_header_controller.js
28264
+ var table_header_controller_default = class extends Controller {
28265
+ headerClick(event) {
28266
+ if (!event.shiftKey)
28267
+ return;
28268
+ const link2 = event.currentTarget;
28269
+ const multiHref = link2.dataset.tableHeaderMultiHref;
28270
+ if (!multiHref)
28271
+ return;
28272
+ event.preventDefault();
28273
+ Turbo.visit(multiHref);
28274
+ }
28275
+ };
28276
+
28277
+ // src/js/controllers/table_column_menu_controller.js
28278
+ var table_column_menu_controller_default = class extends Controller {
28279
+ static targets = ["panel"];
28280
+ connect() {
28281
+ this._onDocClick = this._onDocClick.bind(this);
28282
+ }
28283
+ toggle(event) {
28284
+ event.preventDefault();
28285
+ event.stopPropagation();
28286
+ if (this.hasPanelTarget) {
28287
+ const isNowVisible = !this.panelTarget.classList.toggle("hidden");
28288
+ if (isNowVisible) {
28289
+ document.addEventListener("click", this._onDocClick);
28290
+ this._onKey = (e4) => {
28291
+ if (e4.key === "Escape")
28292
+ this._close();
28293
+ };
28294
+ document.addEventListener("keydown", this._onKey);
28295
+ } else {
28296
+ this._unbind();
28297
+ }
28298
+ }
28299
+ }
28300
+ _close() {
28301
+ if (this.hasPanelTarget)
28302
+ this.panelTarget.classList.add("hidden");
28303
+ this._unbind();
28304
+ }
28305
+ _unbind() {
28306
+ document.removeEventListener("click", this._onDocClick);
28307
+ if (this._onKey) {
28308
+ document.removeEventListener("keydown", this._onKey);
28309
+ this._onKey = null;
28310
+ }
28311
+ }
28312
+ _onDocClick(event) {
28313
+ if (!this.element.contains(event.target))
28314
+ this._close();
28315
+ }
28316
+ };
28317
+
28318
+ // src/js/controllers/capture_url_controller.js
28319
+ var capture_url_controller_default = class extends Controller {
28320
+ connect() {
28321
+ if ("value" in this.element) {
28322
+ this.element.value = window.location.href;
28323
+ }
28324
+ }
28325
+ };
28326
+
28327
+ // src/js/controllers/row_click_controller.js
28328
+ var row_click_controller_default = class extends Controller {
28329
+ click(event) {
28330
+ if (event.target.closest("a, button, input, label, select, textarea, [data-row-click-ignore]")) {
28331
+ return;
28332
+ }
28333
+ this.element.querySelector('[data-row-click-target="show"]')?.click();
28334
+ }
28335
+ };
28336
+
28337
+ // src/js/controllers/view_switcher_controller.js
28338
+ var view_switcher_controller_default = class extends Controller {
28339
+ static values = { cookieName: String, cookiePath: { type: String, default: "/" } };
28340
+ select(event) {
28341
+ const view = event.params.view;
28342
+ if (!view || !this.cookieNameValue)
28343
+ return;
28344
+ const maxAge = 60 * 60 * 24 * 365;
28345
+ const path = this.cookiePathValue || "/";
28346
+ document.cookie = `${this.cookieNameValue}=${encodeURIComponent(view)}; Path=${path}; Max-Age=${maxAge}; SameSite=Lax`;
28347
+ const url = new URL(window.location.href);
28348
+ url.searchParams.delete("view");
28349
+ window.location.href = url.toString();
28350
+ }
28351
+ };
28352
+
28353
+ // src/js/controllers/autosubmit_controller.js
28354
+ var autosubmit_controller_default = class extends Controller {
28355
+ static values = { delay: { type: Number, default: 300 } };
28356
+ connect() {
28357
+ this._timer = null;
28358
+ }
28359
+ disconnect() {
28360
+ if (this._timer)
28361
+ clearTimeout(this._timer);
28362
+ }
28363
+ submit() {
28364
+ if (this._timer)
28365
+ clearTimeout(this._timer);
28366
+ this._timer = setTimeout(() => {
28367
+ this.element.closest("form")?.requestSubmit();
28368
+ }, this.delayValue);
28369
+ }
28370
+ };
28371
+
28036
28372
  // src/js/controllers/register_controllers.js
28037
28373
  function register_controllers_default(application2) {
28038
28374
  application2.register("password-visibility", password_visibility_controller_default);
@@ -28060,6 +28396,14 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28060
28396
  application2.register("filter-panel", filter_panel_controller_default);
28061
28397
  application2.register("textarea-autogrow", textarea_autogrow_controller_default);
28062
28398
  application2.register("clipboard", clipboard_controller_default);
28399
+ application2.register("icon-rail", icon_rail_controller_default);
28400
+ application2.register("icon-rail-flyout", icon_rail_flyout_controller_default);
28401
+ application2.register("table-header", table_header_controller_default);
28402
+ application2.register("table-column-menu", table_column_menu_controller_default);
28403
+ application2.register("capture-url", capture_url_controller_default);
28404
+ application2.register("row-click", row_click_controller_default);
28405
+ application2.register("view-switcher", view_switcher_controller_default);
28406
+ application2.register("autosubmit", autosubmit_controller_default);
28063
28407
  }
28064
28408
 
28065
28409
  // src/js/turbo/turbo_actions.js