stimeo-ui 0.9.0 → 0.10.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -0
  3. data/dist/controllers/alert_dialog_controller.js +17 -7
  4. data/dist/controllers/announcer_controller.js +1 -1
  5. data/dist/controllers/bulk_select_controller.js +2 -2
  6. data/dist/controllers/collapsible_controller.js +1 -1
  7. data/dist/controllers/command_palette_controller.js +15 -5
  8. data/dist/controllers/confirm_controller.js +15 -5
  9. data/dist/controllers/countdown_controller.js +1 -1
  10. data/dist/controllers/data_grid_controller.js +1 -1
  11. data/dist/controllers/dialog_controller.js +15 -5
  12. data/dist/controllers/direct_upload_controller.js +1 -5
  13. data/dist/controllers/drawer_controller.js +17 -7
  14. data/dist/controllers/focus_controller.js +45 -7
  15. data/dist/controllers/form_field_controller.js +1 -1
  16. data/dist/controllers/menubar_controller.js +2 -2
  17. data/dist/controllers/multi_select_controller.js +1 -1
  18. data/dist/controllers/overflow_menu_controller.js +2 -2
  19. data/dist/controllers/password_reveal_controller.js +102 -3
  20. data/dist/controllers/password_strength_controller.js +287 -42
  21. data/dist/controllers/resizable_controller.js +1 -1
  22. data/dist/controllers/scroll_restore_controller.js +127 -27
  23. data/dist/controllers/scroll_visibility_controller.js +167 -14
  24. data/dist/controllers/sidebar_controller.js +15 -5
  25. data/dist/controllers/step_indicator_controller.js +0 -5
  26. data/dist/controllers/theme_controller.js +151 -40
  27. data/dist/controllers/toast_controller.js +2 -2
  28. data/dist/controllers/transition_controller.js +79 -15
  29. data/dist/index.js +740 -178
  30. data/dist/positioning/index.js +84 -25
  31. data/lib/stimeo/ui/version.rb +1 -1
  32. metadata +2 -2
@@ -2,6 +2,20 @@ import { computePosition, autoUpdate, offset, flip, shift } from '@floating-ui/d
2
2
  import { Controller } from '@hotwired/stimulus';
3
3
 
4
4
  // src/positioning/index.ts
5
+ var PLACEMENTS = /* @__PURE__ */ new Set([
6
+ "top",
7
+ "top-start",
8
+ "top-end",
9
+ "right",
10
+ "right-start",
11
+ "right-end",
12
+ "bottom",
13
+ "bottom-start",
14
+ "bottom-end",
15
+ "left",
16
+ "left-start",
17
+ "left-end"
18
+ ]);
5
19
  var AnchoredController = class extends Controller {
6
20
  static targets = ["anchor", "floating"];
7
21
  static values = {
@@ -16,10 +30,18 @@ var AnchoredController = class extends Controller {
16
30
  static events = ["position"];
17
31
  /** `autoUpdate` cleanup while tracking; `null` when detached. */
18
32
  #stop = null;
33
+ /** Identity of the live attach; `null` when detached — see {@link #onComputed}. */
34
+ #attachId = null;
19
35
  /** True between connect and disconnect (Stimulus may fire value callbacks before connect). */
20
36
  #connected = false;
21
- /** Serialized options of the live observer, or `null` when detached — see {@link #sync}. */
22
- #appliedKey = null;
37
+ /**
38
+ * Serialized options as of the last reconcile, empty only before the first one —
39
+ * a value no serialization produces, so it can never match a real key.
40
+ */
41
+ #appliedKey = "";
42
+ /** Elements the live observer holds, or `null` when detached — see {@link #sync}. */
43
+ #appliedAnchor = null;
44
+ #appliedFloating = null;
23
45
  connect() {
24
46
  this.#connected = true;
25
47
  this.#sync();
@@ -28,8 +50,21 @@ var AnchoredController = class extends Controller {
28
50
  this.#connected = false;
29
51
  this.#sync();
30
52
  }
31
- // Every value change (active or an option) re-syncs. `#sync` is idempotent and
32
- // order-independent, so no Value has to be declared in a particular position.
53
+ // Every value change (active or an option) and every target arrival or
54
+ // departure re-syncs. `#sync` is idempotent and order-independent, so no Value
55
+ // has to be declared in a particular position.
56
+ anchorTargetConnected() {
57
+ this.#sync();
58
+ }
59
+ anchorTargetDisconnected() {
60
+ this.#sync();
61
+ }
62
+ floatingTargetConnected() {
63
+ this.#sync();
64
+ }
65
+ floatingTargetDisconnected() {
66
+ this.#sync();
67
+ }
33
68
  activeValueChanged() {
34
69
  this.#sync();
35
70
  }
@@ -54,48 +89,72 @@ var AnchoredController = class extends Controller {
54
89
  /** Current Values mapped to the positioning engine's options. */
55
90
  get #options() {
56
91
  return {
57
- placement: this.placementValue,
58
- offset: this.offsetValue,
92
+ // Narrow every free-form Value to what the engine can use. A placement
93
+ // outside the set would be published on the hook as a resolved side it is
94
+ // not, and a non-finite distance poisons the coordinate it feeds until the
95
+ // browser drops the whole declaration and leaves that axis unplaced.
96
+ placement: PLACEMENTS.has(this.placementValue) ? this.placementValue : "bottom",
97
+ offset: Number.isFinite(this.offsetValue) ? this.offsetValue : 0,
59
98
  flip: this.flipValue,
60
99
  shift: this.shiftValue,
61
- padding: this.paddingValue,
62
- // Narrow the free-form Value to the engine's union; anything but "fixed"
63
- // falls back to the default "absolute".
100
+ padding: Number.isFinite(this.paddingValue) ? this.paddingValue : 0,
64
101
  strategy: this.strategyValue === "fixed" ? "fixed" : "absolute"
65
102
  };
66
103
  }
67
104
  /**
68
105
  * Reconciles the live observer with the desired state — track iff connected,
69
- * `active`, and both targets exist — re-attaching only when that state or the
70
- * options actually changed. Stimulus fires the value-changed callbacks on
71
- * connect in declaration order and may run them before or after `connect()`;
72
- * keying on the applied options collapses that whole burst (in any order) to a
73
- * single attach, while an option change at runtime re-attaches exactly once. So
74
- * correctness never depends on `active` being declared last.
106
+ * `active`, and both targets exist — re-attaching only when that state, the
107
+ * options, or the elements actually changed. Stimulus fires the value-changed
108
+ * callbacks on connect in declaration order and may run them before or after
109
+ * `connect()`; keying on what is applied collapses that whole burst (in any
110
+ * order) to a single attach, while an option or target change at runtime
111
+ * re-attaches exactly once. So correctness never depends on `active` being
112
+ * declared last. The elements are part of the key because the engine holds the
113
+ * pair it was handed: a swap leaves the same options behind and would otherwise
114
+ * keep measuring the node that just left the document.
75
115
  */
76
116
  #sync() {
77
117
  const shouldTrack = this.#connected && this.activeValue && this.hasAnchorTarget && this.hasFloatingTarget;
78
- const key = shouldTrack ? JSON.stringify(this.#options) : null;
79
- if (key === this.#appliedKey) return;
118
+ const anchor = shouldTrack ? this.anchorTarget : null;
119
+ const floating = shouldTrack ? this.floatingTarget : null;
120
+ const key = JSON.stringify(this.#options);
121
+ if (key === this.#appliedKey && anchor === this.#appliedAnchor && floating === this.#appliedFloating) {
122
+ return;
123
+ }
80
124
  this.#detach();
81
125
  this.#appliedKey = key;
82
- if (shouldTrack) this.#attach();
126
+ this.#appliedAnchor = anchor;
127
+ this.#appliedFloating = floating;
128
+ if (anchor && floating) this.#attach(anchor, floating);
83
129
  }
84
- #attach() {
130
+ #attach(anchor, floating) {
131
+ const id = /* @__PURE__ */ Symbol("anchored-attach");
132
+ this.#attachId = id;
85
133
  this.#stop = attachPositioning(
86
- this.anchorTarget,
87
- this.floatingTarget,
134
+ anchor,
135
+ floating,
88
136
  this.#options,
89
- (result) => this.#onComputed(result)
137
+ (result) => this.#onComputed(id, floating, result)
90
138
  );
91
139
  }
92
140
  #detach() {
93
141
  this.#stop?.();
94
142
  this.#stop = null;
143
+ this.#attachId = null;
95
144
  }
96
- /** Reflects the resolved side onto the CSS hook and announces the placement. */
97
- #onComputed(result) {
98
- this.floatingTarget.setAttribute("data-anchored-placement", result.placement);
145
+ /**
146
+ * Reflects the resolved side onto the CSS hook and announces the placement.
147
+ *
148
+ * The pass carries the attach that started it and the element it positioned,
149
+ * and lands only while that attach is still the live one: `autoUpdate`'s
150
+ * cleanup stops further updates but cannot cancel one already computing, so a
151
+ * pass superseded mid-flight would otherwise write and dispatch afterwards.
152
+ * The element cannot stand in for that identity — an option change re-attaches
153
+ * against the very same pair.
154
+ */
155
+ #onComputed(id, floating, result) {
156
+ if (id !== this.#attachId) return;
157
+ floating.setAttribute("data-anchored-placement", result.placement);
99
158
  this.dispatch("position", {
100
159
  detail: { placement: result.placement, x: result.x, y: result.y }
101
160
  });
@@ -5,6 +5,6 @@ module Stimeo
5
5
  # Kept in lockstep with package.json (stable versions share the same
6
6
  # notation; a prerelease maps npm `-beta.N` ⇔ gem `.pre.beta.N` because
7
7
  # RubyGems forbids dashes). Bump both together.
8
- VERSION = "0.9.0"
8
+ VERSION = "0.10.0"
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stimeo-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stimeo Labs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-28 00:00:00.000000000 Z
11
+ date: 2026-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties