stimeo-ui 0.15.0 → 0.17.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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +209 -0
  3. data/README.md +128 -1
  4. data/dist/cable/index.js +392 -417
  5. data/dist/controllers/accordion_controller.js +228 -28
  6. data/dist/controllers/alert_dialog_controller.js +862 -221
  7. data/dist/controllers/announcer_controller.js +135 -191
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +295 -110
  10. data/dist/controllers/avatar_controller.js +172 -106
  11. data/dist/controllers/breadcrumb_controller.js +0 -129
  12. data/dist/controllers/bulk_select_controller.js +264 -53
  13. data/dist/controllers/calendar_controller.js +346 -180
  14. data/dist/controllers/carousel_controller.js +587 -300
  15. data/dist/controllers/character_counter_controller.js +271 -143
  16. data/dist/controllers/checkbox_controller.js +77 -75
  17. data/dist/controllers/clipboard_controller.js +187 -108
  18. data/dist/controllers/collapsible_controller.js +374 -108
  19. data/dist/controllers/color_picker_controller.js +183 -127
  20. data/dist/controllers/combobox_controller.js +426 -109
  21. data/dist/controllers/command_palette_controller.js +994 -350
  22. data/dist/controllers/conditional_fields_controller.js +212 -125
  23. data/dist/controllers/confirm_controller.js +939 -245
  24. data/dist/controllers/context_menu_controller.js +275 -127
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +235 -97
  27. data/dist/controllers/currency_input_controller.js +241 -118
  28. data/dist/controllers/data_grid_controller.js +229 -135
  29. data/dist/controllers/date_range_picker_controller.js +359 -165
  30. data/dist/controllers/dialog_controller.js +887 -225
  31. data/dist/controllers/direct_upload_controller.js +80 -142
  32. data/dist/controllers/dirty_form_controller.js +26 -47
  33. data/dist/controllers/dismissible_controller.js +175 -20
  34. data/dist/controllers/drawer_controller.js +963 -336
  35. data/dist/controllers/dropdown_controller.js +342 -85
  36. data/dist/controllers/editable_controller.js +182 -58
  37. data/dist/controllers/empty_state_controller.js +67 -53
  38. data/dist/controllers/file_dropzone_controller.js +426 -229
  39. data/dist/controllers/filter_controller.js +78 -36
  40. data/dist/controllers/flash_controller.js +328 -211
  41. data/dist/controllers/focus_controller.js +654 -239
  42. data/dist/controllers/form_field_controller.js +153 -136
  43. data/dist/controllers/form_validation_controller.js +29 -96
  44. data/dist/controllers/frame_loading_controller.js +263 -198
  45. data/dist/controllers/highlight_controller.js +119 -70
  46. data/dist/controllers/hover_card_controller.js +335 -118
  47. data/dist/controllers/idle_controller.js +316 -65
  48. data/dist/controllers/input_mask_controller.js +116 -61
  49. data/dist/controllers/intersection_controller.js +147 -103
  50. data/dist/controllers/lazy_frame_controller.js +9 -56
  51. data/dist/controllers/listbox_controller.js +413 -153
  52. data/dist/controllers/local_time_controller.js +54 -64
  53. data/dist/controllers/masonry_controller.js +70 -95
  54. data/dist/controllers/menu_controller.js +254 -167
  55. data/dist/controllers/menubar_controller.js +206 -331
  56. data/dist/controllers/meter_controller.js +159 -71
  57. data/dist/controllers/multi_select_controller.js +797 -388
  58. data/dist/controllers/navigation_menu_controller.js +156 -231
  59. data/dist/controllers/nested_form_controller.js +373 -174
  60. data/dist/controllers/network_status_controller.js +241 -45
  61. data/dist/controllers/number_input_controller.js +387 -227
  62. data/dist/controllers/optimistic_controller.js +28 -75
  63. data/dist/controllers/otp_controller.js +344 -196
  64. data/dist/controllers/overflow_indicator_controller.js +167 -93
  65. data/dist/controllers/overflow_menu_controller.js +408 -277
  66. data/dist/controllers/pagination_controller.js +342 -120
  67. data/dist/controllers/password_reveal_controller.js +424 -99
  68. data/dist/controllers/password_strength_controller.js +99 -153
  69. data/dist/controllers/persist_controller.js +252 -96
  70. data/dist/controllers/pointer_drag_controller.js +263 -164
  71. data/dist/controllers/popover_controller.js +267 -94
  72. data/dist/controllers/portal_controller.js +209 -72
  73. data/dist/controllers/preview_guard_controller.js +243 -112
  74. data/dist/controllers/progress_controller.js +132 -59
  75. data/dist/controllers/radio_group_controller.js +138 -133
  76. data/dist/controllers/range_slider_controller.js +245 -112
  77. data/dist/controllers/rating_controller.js +429 -194
  78. data/dist/controllers/read_more_controller.js +185 -47
  79. data/dist/controllers/reading_progress_controller.js +147 -135
  80. data/dist/controllers/relative_time_controller.js +145 -110
  81. data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  82. data/dist/controllers/resizable_controller.js +294 -118
  83. data/dist/controllers/roving_controller.js +39 -84
  84. data/dist/controllers/scroll_area_controller.js +428 -221
  85. data/dist/controllers/scroll_restore_controller.js +0 -53
  86. data/dist/controllers/scroll_visibility_controller.js +395 -146
  87. data/dist/controllers/scrollspy_controller.js +181 -236
  88. data/dist/controllers/separator_controller.js +288 -132
  89. data/dist/controllers/sidebar_controller.js +1071 -375
  90. data/dist/controllers/skeleton_controller.js +248 -92
  91. data/dist/controllers/slider_controller.js +211 -96
  92. data/dist/controllers/smart_sticky_header_controller.js +163 -54
  93. data/dist/controllers/sortable_controller.js +13 -110
  94. data/dist/controllers/spinner_controller.js +277 -161
  95. data/dist/controllers/step_indicator_controller.js +107 -53
  96. data/dist/controllers/stepper_controller.js +171 -48
  97. data/dist/controllers/stick_to_bottom_controller.js +310 -116
  98. data/dist/controllers/sticky_observer_controller.js +214 -49
  99. data/dist/controllers/submit_once_controller.js +294 -192
  100. data/dist/controllers/switch_controller.js +107 -43
  101. data/dist/controllers/tabs_controller.js +50 -25
  102. data/dist/controllers/tags_input_controller.js +290 -149
  103. data/dist/controllers/textarea_autosize_controller.js +71 -58
  104. data/dist/controllers/theme_controller.js +148 -80
  105. data/dist/controllers/time_picker_controller.js +162 -81
  106. data/dist/controllers/toast_controller.js +365 -193
  107. data/dist/controllers/toggle_group_controller.js +194 -99
  108. data/dist/controllers/toolbar_controller.js +92 -90
  109. data/dist/controllers/tooltip_controller.js +185 -125
  110. data/dist/controllers/transition_controller.js +123 -115
  111. data/dist/controllers/tree_view_controller.js +248 -290
  112. data/dist/index.js +8649 -8572
  113. data/dist/positioning/index.js +148 -45
  114. data/lib/stimeo/ui/version.rb +1 -1
  115. metadata +3 -3
@@ -1,8 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/smart_sticky_header_controller.ts
4
-
5
- // src/utils/declared_value.ts
6
3
  function parseDeclared(raw, parse, fallback) {
7
4
  try {
8
5
  return parse(raw);
@@ -22,14 +19,8 @@ function validSelector(element, raw, fallback) {
22
19
  );
23
20
  }
24
21
 
25
- // src/utils/frame_coalescer.ts
26
22
  var FrameCoalescer = class {
27
23
  #frame = null;
28
- /**
29
- * Runs `run` on the next frame, unless a frame is already pending — the first
30
- * request of a burst wins and the rest are dropped. The pending frame is
31
- * released before `run`, so `run` may request the next one.
32
- */
33
24
  schedule(run) {
34
25
  if (this.#frame !== null) return;
35
26
  this.#frame = requestAnimationFrame(() => {
@@ -37,14 +28,6 @@ var FrameCoalescer = class {
37
28
  run();
38
29
  });
39
30
  }
40
- /**
41
- * Drops the pending frame, and reaches the platform only when there is one.
42
- *
43
- * There is no handle value that stands for "nothing pending":
44
- * `cancelAnimationFrame` takes an `unsigned long`, so a negative placeholder
45
- * arrives as a large positive number that the same allocator can hand out, and
46
- * an idle cancel would then drop a frame belonging to someone else.
47
- */
48
31
  cancel() {
49
32
  if (this.#frame === null) return;
50
33
  cancelAnimationFrame(this.#frame);
@@ -52,7 +35,115 @@ var FrameCoalescer = class {
52
35
  }
53
36
  };
54
37
 
55
- // src/utils/scroll_source.ts
38
+ var ListenerSet = class {
39
+ #abort = new AbortController();
40
+ add(target, type, handler, options) {
41
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
42
+ }
43
+ dispose() {
44
+ this.#abort.abort();
45
+ this.#abort = new AbortController();
46
+ }
47
+ };
48
+
49
+ var MicrotaskCoalescer = class {
50
+ #run;
51
+ #queued = false;
52
+ #active = false;
53
+ #generation = 0;
54
+ constructor(run) {
55
+ this.#run = run;
56
+ }
57
+ activate() {
58
+ this.#active = true;
59
+ }
60
+ cancel() {
61
+ this.#active = false;
62
+ this.#queued = false;
63
+ this.#generation += 1;
64
+ }
65
+ schedule() {
66
+ if (!this.#active || this.#queued) return;
67
+ this.#queued = true;
68
+ const generation = this.#generation;
69
+ queueMicrotask(() => {
70
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
71
+ this.#queued = false;
72
+ this.#run();
73
+ });
74
+ }
75
+ };
76
+
77
+ var MorphRenderWatcher = class {
78
+ #listeners = new ListenerSet();
79
+ #pass;
80
+ #element = null;
81
+ #onMorph = () => this.#pass.schedule();
82
+ constructor(run) {
83
+ this.#pass = new MicrotaskCoalescer(run);
84
+ }
85
+ observe(element) {
86
+ if (this.#element === element) return;
87
+ this.disconnect();
88
+ this.#element = element;
89
+ this.#pass.activate();
90
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
91
+ }
92
+ schedule() {
93
+ this.#pass.schedule();
94
+ }
95
+ disconnect() {
96
+ this.#listeners.dispose();
97
+ this.#pass.cancel();
98
+ this.#element = null;
99
+ }
100
+ };
101
+
102
+ var NUMBER_BOUNDS = {
103
+ finite: { finite: true },
104
+ nonNegative: { finite: true, min: 0 }};
105
+ function matchesNumberBounds(value, bounds) {
106
+ if (!Number.isFinite(value)) {
107
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
108
+ if (direction === null) return false;
109
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
110
+ }
111
+ if (bounds.min !== void 0 && value < bounds.min) return false;
112
+ if (bounds.max !== void 0 && value > bounds.max) return false;
113
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
114
+ if (bounds.integer && !Number.isInteger(value)) return false;
115
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
116
+ return true;
117
+ }
118
+
119
+ function readNumber(raw, fallback, bounds) {
120
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
121
+ }
122
+
123
+ var NumberValueReader = class {
124
+ #lastRejected = /* @__PURE__ */ new Map();
125
+ read(owner, name, raw, fallback, bounds) {
126
+ const resolved = readNumber(raw, fallback, bounds);
127
+ if (matchesNumberBounds(raw, bounds)) {
128
+ this.#lastRejected.delete(name);
129
+ return resolved;
130
+ }
131
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
132
+ const literal = owner.element.getAttribute(attribute);
133
+ if (literal === null) {
134
+ this.#lastRejected.delete(name);
135
+ return resolved;
136
+ }
137
+ if (this.#lastRejected.get(name) !== literal) {
138
+ this.#lastRejected.set(name, literal);
139
+ console.warn(
140
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
141
+ );
142
+ }
143
+ return resolved;
144
+ }
145
+ };
146
+
56
147
  function resolveScrollContainer(selector) {
57
148
  const match = selector ? document.querySelector(selector) : null;
58
149
  return match instanceof HTMLElement ? match : null;
@@ -64,63 +155,64 @@ function scrollOffset(source) {
64
155
  return source === window ? window.scrollY ?? window.pageYOffset ?? 0 : source.scrollTop;
65
156
  }
66
157
 
67
- // src/controllers/smart_sticky_header_controller.ts
68
158
  var DEFAULT_OFFSET = 80;
69
- var SmartStickyHeaderController = class extends Controller {
159
+ var SmartStickyHeaderController = class _SmartStickyHeaderController extends Controller {
160
+ #numbers = new NumberValueReader();
70
161
  static values = {
71
162
  containerSelector: { type: String, default: "" },
72
163
  offset: { type: Number, default: DEFAULT_OFFSET },
73
164
  tolerance: { type: Number, default: 4 }
74
165
  };
166
+ static valueConstraints = {
167
+ offset: NUMBER_BOUNDS.finite,
168
+ tolerance: NUMBER_BOUNDS.nonNegative
169
+ };
75
170
  static events = ["change"];
76
- #connected = false;
77
- /** Coalesces scroll bursts into one measurement per frame. */
78
171
  #frames = new FrameCoalescer();
79
- /** The scroll source resolved at connect — disconnect must unbind the SAME node. */
80
172
  #scrollerEl = window;
81
- /** The validated `containerSelector`, or `""` when the declaration cannot be parsed. */
82
173
  #containerSelector = "";
83
174
  #lastY = 0;
84
- /** Last published state, so `change` fires only on transitions. */
85
175
  #hidden = null;
86
- /**
87
- * The depth that never hides. A declaration that is not a finite number reads
88
- * as the default, so the comparison path never sees `NaN` — which would
89
- * answer `false` to every comparison and hide the header inside the very
90
- * zone the value exists to protect.
91
- */
92
176
  get #offset() {
93
- return Number.isFinite(this.offsetValue) ? this.offsetValue : DEFAULT_OFFSET;
177
+ return this.#safeOffset;
94
178
  }
95
179
  #onScroll = () => this.#frames.schedule(() => this.#measure());
96
- /**
97
- * Focus inside a hidden header must reveal it (WCAG 2.4.7 / 2.4.11); the
98
- * hold while focus *stays* inside is the `#apply` hide invariant.
99
- */
100
180
  #onFocusin = () => this.#apply(false);
101
- /** Validates `containerSelector` once so connect never parses a selector that throws. */
102
181
  containerSelectorValueChanged() {
103
182
  this.#containerSelector = validSelector(this.element, this.containerSelectorValue, "");
183
+ this.#morphRender.schedule();
104
184
  }
105
- /** Re-decides when application code (or a Turbo morph) changes `offset` at runtime. */
106
185
  offsetValueChanged() {
107
- if (this.#connected) this.#measure();
186
+ this.#morphRender.schedule();
108
187
  }
188
+ #morphRender = new MorphRenderWatcher(() => {
189
+ this.#followSource();
190
+ this.#measure();
191
+ });
109
192
  connect() {
193
+ this.#morphRender.observe(this.element);
110
194
  this.#hidden = null;
111
- this.#scrollerEl = resolveScrollSource(this.#containerSelector);
112
- this.#lastY = scrollOffset(this.#scrollerEl);
113
- this.#scrollerEl.addEventListener("scroll", this.#onScroll, { passive: true });
195
+ this.#listenTo(resolveScrollSource(this.#containerSelector));
114
196
  this.element.addEventListener("focusin", this.#onFocusin);
115
197
  this.#apply(false, false);
116
- this.#connected = true;
117
198
  }
118
199
  disconnect() {
119
- this.#connected = false;
200
+ this.#morphRender.disconnect();
120
201
  this.#scrollerEl.removeEventListener("scroll", this.#onScroll);
121
202
  this.element.removeEventListener("focusin", this.#onFocusin);
122
203
  this.#frames.cancel();
123
204
  }
205
+ #followSource() {
206
+ const source = resolveScrollSource(this.#containerSelector);
207
+ if (source === this.#scrollerEl) return;
208
+ this.#scrollerEl.removeEventListener("scroll", this.#onScroll);
209
+ this.#listenTo(source);
210
+ }
211
+ #listenTo(source) {
212
+ this.#scrollerEl = source;
213
+ this.#lastY = scrollOffset(source);
214
+ source.addEventListener("scroll", this.#onScroll, { passive: true });
215
+ }
124
216
  #measure() {
125
217
  const y = scrollOffset(this.#scrollerEl);
126
218
  if (y <= this.#offset) {
@@ -129,25 +221,42 @@ var SmartStickyHeaderController = class extends Controller {
129
221
  return;
130
222
  }
131
223
  const delta = y - this.#lastY;
132
- if (Math.abs(delta) < this.toleranceValue) return;
224
+ if (this.#isJitter(delta)) {
225
+ this.#apply(this.#hidden ?? false);
226
+ return;
227
+ }
133
228
  this.#lastY = y;
134
229
  this.#apply(delta > 0);
135
230
  }
136
- /**
137
- * Reflects the state onto the hook and emits `change` on transitions.
138
- *
139
- * @param notify - whether a transition announces itself. The reflection
140
- * `connect()` performs is the current state, not a change.
141
- */
231
+ #isJitter(delta) {
232
+ return Math.abs(delta) < this.#safeTolerance;
233
+ }
142
234
  #apply(hidden, notify = true) {
143
235
  if (hidden && this.element.contains(document.activeElement)) return;
144
- if (hidden === this.#hidden) return;
236
+ const changed = hidden !== this.#hidden;
145
237
  this.#hidden = hidden;
146
238
  this.element.setAttribute("data-header-hidden", hidden ? "true" : "false");
147
- if (notify) this.dispatch("change", { detail: { hidden } });
239
+ if (notify && changed) this.dispatch("change", { detail: { hidden } });
240
+ }
241
+ get #safeOffset() {
242
+ return this.#numbers.read(
243
+ this,
244
+ "offset",
245
+ this.offsetValue,
246
+ _SmartStickyHeaderController.values.offset.default,
247
+ _SmartStickyHeaderController.valueConstraints.offset
248
+ );
249
+ }
250
+ get #safeTolerance() {
251
+ return this.#numbers.read(
252
+ this,
253
+ "tolerance",
254
+ this.toleranceValue,
255
+ _SmartStickyHeaderController.values.tolerance.default,
256
+ _SmartStickyHeaderController.valueConstraints.tolerance
257
+ );
148
258
  }
149
259
  };
150
260
 
151
261
  export { SmartStickyHeaderController };
152
- //# sourceMappingURL=smart_sticky_header_controller.js.map
153
262
  //# sourceMappingURL=smart_sticky_header_controller.js.map
@@ -1,8 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/sortable_controller.ts
4
-
5
- // src/utils/announce.ts
6
3
  function announce(message, options = {}) {
7
4
  const text = message.trim();
8
5
  if (text.length === 0) return;
@@ -19,32 +16,26 @@ function fillTemplate(template, values) {
19
16
  });
20
17
  }
21
18
 
22
- // src/utils/logical_scroll.ts
23
19
  function isRtl(element) {
24
20
  return window.getComputedStyle(element).direction === "rtl";
25
21
  }
26
22
 
27
- // src/utils/microtask_coalescer.ts
28
23
  var MicrotaskCoalescer = class {
29
24
  #run;
30
25
  #queued = false;
31
26
  #active = false;
32
27
  #generation = 0;
33
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
34
28
  constructor(run) {
35
29
  this.#run = run;
36
30
  }
37
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
38
31
  activate() {
39
32
  this.#active = true;
40
33
  }
41
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
42
34
  cancel() {
43
35
  this.#active = false;
44
36
  this.#queued = false;
45
37
  this.#generation += 1;
46
38
  }
47
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
48
39
  schedule() {
49
40
  if (!this.#active || this.#queued) return;
50
41
  this.#queued = true;
@@ -57,7 +48,17 @@ var MicrotaskCoalescer = class {
57
48
  }
58
49
  };
59
50
 
60
- // src/controllers/sortable_controller.ts
51
+ var TransientHooks = class {
52
+ #attributes;
53
+ constructor(options) {
54
+ this.#attributes = options.attributes;
55
+ }
56
+ reset(element) {
57
+ for (const attribute of this.#attributes) element.removeAttribute(attribute);
58
+ }
59
+ };
60
+
61
+ var TRANSIENT = new TransientHooks({ attributes: ["data-sortable-dragging"] });
61
62
  var SortableController = class extends Controller {
62
63
  static targets = ["list", "item"];
63
64
  static values = {
@@ -69,10 +70,9 @@ var SortableController = class extends Controller {
69
70
  };
70
71
  static events = ["reorder"];
71
72
  #session = null;
72
- /** Defers the lost-item check to after the mutation batch (see below). */
73
73
  #settle = new MicrotaskCoalescer(() => this.#dropLostSession());
74
74
  connect() {
75
- this.element.removeAttribute("data-sortable-dragging");
75
+ TRANSIENT.reset(this.element);
76
76
  this.#settle.activate();
77
77
  this.element.addEventListener("stimeo--pointer-drag:start", this.#onDragStart);
78
78
  this.element.addEventListener("stimeo--pointer-drag:move", this.#onDragMove);
@@ -88,17 +88,6 @@ var SortableController = class extends Controller {
88
88
  this.#session = null;
89
89
  this.element.removeAttribute("data-sortable-dragging");
90
90
  }
91
- /**
92
- * Ends a session whose item left the item set.
93
- *
94
- * The controller's own reorder detaches and reattaches the item inside one
95
- * mutation batch, so the loss is only real once the batch has settled — the
96
- * check therefore runs a microtask later and asks whether the item is a target
97
- * again. Without it a deleted row (a broadcast that drops it from the board)
98
- * or a morph that strips the item's target attribute would hold the
99
- * one-at-a-time session for the rest of the page's life, with the root hook
100
- * stuck on and every later grab refused with no way out.
101
- */
102
91
  itemTargetDisconnected(item) {
103
92
  if (this.#session?.item !== item) return;
104
93
  this.#settle.schedule();
@@ -112,7 +101,6 @@ var SortableController = class extends Controller {
112
101
  this.element.removeAttribute("data-sortable-dragging");
113
102
  if (session.item.isConnected) this.#restore(session, items);
114
103
  }
115
- /** Picks the item up: remembers its neighbours and announces the grab. */
116
104
  #onDragStart = (event) => {
117
105
  if (this.#session) return;
118
106
  const items = this.#items();
@@ -140,7 +128,6 @@ var SortableController = class extends Controller {
140
128
  this.#followPointer(session, detail, items);
141
129
  }
142
130
  };
143
- /** Drops the item: announces, then reports `reorder` if the position changed. */
144
131
  #onDragEnd = (event) => {
145
132
  const session = this.#session;
146
133
  if (!session) return;
@@ -155,7 +142,6 @@ var SortableController = class extends Controller {
155
142
  this.dispatch("reorder", { detail: { item: session.item, from, to } });
156
143
  }
157
144
  };
158
- /** Restores the pickup position (Escape / OS `pointercancel`). */
159
145
  #onDragCancel = (event) => {
160
146
  const session = this.#session;
161
147
  if (!session) return;
@@ -166,16 +152,6 @@ var SortableController = class extends Controller {
166
152
  this.#restore(session, items);
167
153
  this.#announce("canceled", session.item);
168
154
  };
169
- /**
170
- * The slot the item was picked up from, read back through the neighbours it
171
- * had then — `null` when neither survives and the item itself is gone.
172
- *
173
- * The anchor is the item that followed it, so restoring means "before that one
174
- * again". When the anchor was deleted mid-drag the predecessor answers the
175
- * same question from the other side. With both gone the item's current slot is
176
- * the honest answer: nothing is known to have moved, so no reorder is reported
177
- * and a cancel leaves the item where it is.
178
- */
179
155
  #pickupSlot(session, items) {
180
156
  const others = items.filter((candidate) => candidate !== session.item);
181
157
  if (session.anchor) {
@@ -189,7 +165,6 @@ var SortableController = class extends Controller {
189
165
  const here = items.indexOf(session.item);
190
166
  return here === -1 ? null : here;
191
167
  }
192
- /** Puts the item back where it was picked up from. */
193
168
  #restore(session, items) {
194
169
  const slot = this.#pickupSlot(session, items);
195
170
  if (slot === null) return;
@@ -199,17 +174,6 @@ var SortableController = class extends Controller {
199
174
  slot
200
175
  );
201
176
  }
202
- /**
203
- * Keyboard stepping: `pointer-drag` reports *cumulative* synthetic deltas, so
204
- * the difference from the last consumed value is one arrow press — its sign is
205
- * the direction. Cross-axis arrows never change the primary delta (no move).
206
- *
207
- * The sign is physical (`ArrowRight` is always `+dx`), so a right-to-left row
208
- * has to invert it: there, moving the item rightward means moving it *earlier*
209
- * in the DOM. Skipping this would also split the two halves of one keypress —
210
- * `roving` already moves focus logically, so the same arrow would send the
211
- * focus and the grabbed item opposite ways.
212
- */
213
177
  #stepFromKeyboard(session, detail, items) {
214
178
  const primary = Number(this.#isVertical ? detail.dy : detail.dx) || 0;
215
179
  const delta = primary - session.lastPrimary;
@@ -226,18 +190,6 @@ var SortableController = class extends Controller {
226
190
  );
227
191
  this.#announce("moved", session.item);
228
192
  }
229
- /**
230
- * Pointer following: the item moves to the slot whose siblings' midpoints the
231
- * pointer has passed (per `orientation`).
232
- *
233
- * Only siblings that occupy space take part. A row with no layout box — a
234
- * filtered-out item, a `display: none` ancestor, a collapsed `<details>` — is
235
- * reported with an empty rect at the document origin, and its midpoint of `0`
236
- * sits below every pointer position: counted, it would read as passed on the
237
- * very first move and send the item across a slot the pointer never crossed.
238
- * With no laid-out sibling at all there is nothing to compare against and the
239
- * move is skipped entirely.
240
- */
241
193
  #followPointer(session, detail, items) {
242
194
  const pointer = Number(this.#isVertical ? detail.y : detail.x) || 0;
243
195
  const vertical = this.#isVertical;
@@ -259,18 +211,6 @@ var SortableController = class extends Controller {
259
211
  this.#insertAt(session.item, laidOut, target);
260
212
  this.#announce("moved", session.item);
261
213
  }
262
- /**
263
- * Reinserts `item` at `index` among `scope`, relative to the sibling already
264
- * standing there.
265
- *
266
- * The reorder is defined against the items, not against a container: the
267
- * neighbour's own parent is where the item belongs. A `list` target that is
268
- * not the items' parent — or none at all, which the markup contract allows —
269
- * therefore still lands the move in the right place instead of throwing, and
270
- * the last slot is *after the last item* rather than after whatever else the
271
- * container holds (a live region, a footer), which would put the row outside
272
- * the reading order the list publishes.
273
- */
274
214
  #insertAt(item, scope, index) {
275
215
  const clamped = Math.max(0, Math.min(index, scope.length));
276
216
  const ahead = scope[clamped] ?? null;
@@ -284,18 +224,6 @@ var SortableController = class extends Controller {
284
224
  parent.insertBefore(item, ahead ?? neighbour.nextSibling);
285
225
  if (hadFocus) active.focus();
286
226
  }
287
- /**
288
- * Hands one step to the page's shared announcer.
289
- *
290
- * The library carries no live region and no English copy: the wording is the
291
- * consumer's, written into `announceGrabbedText` / `announceMovedText` /
292
- * `announceDroppedText` / `announceCanceledText` with `{name}` / `{position}` /
293
- * `{total}` placeholders, and an unset one announces nothing.
294
- *
295
- * Only transitions reach here — the pickup, a step that actually changed the
296
- * landing slot, and the single end of the session — so a pointer crossing the
297
- * same slot twice or an arrow clamped at an end stays silent.
298
- */
299
227
  #announce(key, item) {
300
228
  const template = this.#announceTemplate(key);
301
229
  if (template.length === 0) return;
@@ -308,7 +236,6 @@ var SortableController = class extends Controller {
308
236
  })
309
237
  );
310
238
  }
311
- /** The consumer's wording for one step, or `""` when they authored none. */
312
239
  #announceTemplate(key) {
313
240
  switch (key) {
314
241
  case "grabbed":
@@ -321,52 +248,28 @@ var SortableController = class extends Controller {
321
248
  return this.announceCanceledTextValue;
322
249
  }
323
250
  }
324
- /** The announced item name: the authored override, else its collapsed text. */
325
251
  #nameOf(item) {
326
- const authored = item.getAttribute("data-stimeo--sortable-name");
252
+ const authored = item.getAttribute(`data-${this.identifier}-name`);
327
253
  if (authored) return authored;
328
254
  return (item.textContent ?? "").replace(/\s+/g, " ").trim();
329
255
  }
330
- /**
331
- * Resolves the sortable item owning a bubbled `pointer-drag` event.
332
- *
333
- * `pointer-drag` dispatches on its own element, and the markup contract puts
334
- * one on each item, so the owner is the item that **is** the target. Matching
335
- * an ancestor instead would make a card's own inner draggable — a knob, a
336
- * split pane, anything the primitive is composed into — drive the card.
337
- */
338
256
  #itemFor(target, items) {
339
257
  if (!target) return null;
340
258
  return items.find((item) => item === target) ?? null;
341
259
  }
342
- /** The items in live DOM order (targets re-query the DOM on every access). */
343
260
  #items() {
344
261
  return this.itemTargets;
345
262
  }
346
- /** The reorder container: the `list` target when present, else the element. */
347
263
  get #list() {
348
264
  return this.hasListTarget ? this.listTarget : this.element;
349
265
  }
350
266
  get #isVertical() {
351
267
  return this.orientationValue !== "horizontal";
352
268
  }
353
- /**
354
- * Whether DOM order runs opposite to the primary coordinate — true only for a
355
- * horizontal row under `dir="rtl"`, where the first item sits at the largest
356
- * `x`. Both drag paths reach this controller in physical terms (`pointer-drag`
357
- * documents its deltas as physical and hands RTL to its consumer, which is
358
- * this controller), so both have to be mapped back onto DOM order here.
359
- *
360
- * Read from the list, the element that lays the items out — never from the
361
- * dragged item, which may carry its own `dir`. The list inherits the computed
362
- * direction, so authoring `dir` on the root works too. A vertical list is
363
- * unaffected: writing direction does not mirror the block axis.
364
- */
365
269
  get #isReversed() {
366
270
  return !this.#isVertical && isRtl(this.#list);
367
271
  }
368
272
  };
369
273
 
370
274
  export { SortableController };
371
- //# sourceMappingURL=sortable_controller.js.map
372
275
  //# sourceMappingURL=sortable_controller.js.map