stimeo-ui 0.14.0 → 0.15.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 (73) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/cable/index.js +60 -14
  3. package/dist/cable/index.js.map +1 -1
  4. package/dist/controllers/announcer_controller.js +88 -61
  5. package/dist/controllers/announcer_controller.js.map +1 -1
  6. package/dist/controllers/auto_submit_controller.js +40 -5
  7. package/dist/controllers/auto_submit_controller.js.map +1 -1
  8. package/dist/controllers/breadcrumb_controller.js +29 -3
  9. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  10. package/dist/controllers/calendar_controller.d.ts +18 -2
  11. package/dist/controllers/calendar_controller.js +20 -3
  12. package/dist/controllers/calendar_controller.js.map +1 -1
  13. package/dist/controllers/carousel_controller.js.map +1 -1
  14. package/dist/controllers/checkbox_controller.js +27 -2
  15. package/dist/controllers/checkbox_controller.js.map +1 -1
  16. package/dist/controllers/conditional_fields_controller.js +29 -6
  17. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  18. package/dist/controllers/count_up_controller.js.map +1 -1
  19. package/dist/controllers/currency_input_controller.d.ts +4 -2
  20. package/dist/controllers/currency_input_controller.js +16 -9
  21. package/dist/controllers/currency_input_controller.js.map +1 -1
  22. package/dist/controllers/date_range_picker_controller.d.ts +7 -0
  23. package/dist/controllers/date_range_picker_controller.js +12 -3
  24. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  25. package/dist/controllers/dirty_form_controller.js +32 -12
  26. package/dist/controllers/dirty_form_controller.js.map +1 -1
  27. package/dist/controllers/flash_controller.js +145 -43
  28. package/dist/controllers/flash_controller.js.map +1 -1
  29. package/dist/controllers/frame_loading_controller.js +29 -6
  30. package/dist/controllers/frame_loading_controller.js.map +1 -1
  31. package/dist/controllers/highlight_controller.js +63 -23
  32. package/dist/controllers/highlight_controller.js.map +1 -1
  33. package/dist/controllers/local_time_controller.js +10 -5
  34. package/dist/controllers/local_time_controller.js.map +1 -1
  35. package/dist/controllers/masonry_controller.d.ts +3 -3
  36. package/dist/controllers/masonry_controller.js +31 -13
  37. package/dist/controllers/masonry_controller.js.map +1 -1
  38. package/dist/controllers/optimistic_controller.js +28 -4
  39. package/dist/controllers/optimistic_controller.js.map +1 -1
  40. package/dist/controllers/overflow_indicator_controller.js +31 -8
  41. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  42. package/dist/controllers/overflow_menu_controller.js +29 -3
  43. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  44. package/dist/controllers/read_more_controller.js +31 -5
  45. package/dist/controllers/read_more_controller.js.map +1 -1
  46. package/dist/controllers/reading_progress_controller.js +65 -19
  47. package/dist/controllers/reading_progress_controller.js.map +1 -1
  48. package/dist/controllers/relative_time_controller.js +10 -5
  49. package/dist/controllers/relative_time_controller.js.map +1 -1
  50. package/dist/controllers/scroll_area_controller.js +70 -26
  51. package/dist/controllers/scroll_area_controller.js.map +1 -1
  52. package/dist/controllers/scroll_restore_controller.js +37 -16
  53. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  54. package/dist/controllers/scroll_visibility_controller.js +49 -29
  55. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  56. package/dist/controllers/scrollspy_controller.js +65 -26
  57. package/dist/controllers/scrollspy_controller.js.map +1 -1
  58. package/dist/controllers/smart_sticky_header_controller.js +49 -25
  59. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  60. package/dist/controllers/stick_to_bottom_controller.js +29 -3
  61. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  62. package/dist/controllers/submit_once_controller.js +29 -6
  63. package/dist/controllers/submit_once_controller.js.map +1 -1
  64. package/dist/controllers/textarea_autosize_controller.js +29 -3
  65. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  66. package/dist/controllers/toast_controller.d.ts +3 -0
  67. package/dist/controllers/toast_controller.js +136 -73
  68. package/dist/controllers/toast_controller.js.map +1 -1
  69. package/dist/controllers/transition_controller.js.map +1 -1
  70. package/dist/index.js +473 -435
  71. package/dist/index.js.map +1 -1
  72. package/dist/inspector/manifest.json +5 -3
  73. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
  While the version is `0.x`, the public API (the `stimeo--*` data attributes) may
8
8
  change between releases.
9
9
 
10
+ ## [0.15.0] - 2026-09-20
11
+
12
+ Minor release with no new components. Shared internals were consolidated across
13
+ five refactors; toast and the locale-aware components change behaviour, so read
14
+ Changed before upgrading. The Inspector manifest stays on schema v12.
15
+
16
+ ### Added
17
+
18
+ - calendar, date-range-picker: a `locale` Value.
19
+
20
+ ### Changed
21
+
22
+ - toast: a toast whose dismiss deadline has already lapsed is no longer removed
23
+ the moment a pointer or focus enters it. It stays, marked `data-paused="true"`,
24
+ and leaves 1ms after the last hold is released — so focus never drops to
25
+ `<body>` from under the close button it was on.
26
+ - calendar, date-range-picker: the month label resolves its locale as the
27
+ `locale` Value, then the nearest `lang` (the element itself included), then the
28
+ runtime default — instead of `<html lang>` with an `"en"` fallback.
29
+ - currency-input: `locale` no longer defaults to `"en-US"` and resolves the same
30
+ way, so a page under `<html lang="de">` reads and writes `1.234,56`. The hidden
31
+ value stays in its ASCII normal form, and a declaration `Intl` rejects still
32
+ falls back to `"en-US"`.
33
+ - auto-submit: the `stimeo--announcer:announce` it bridges carries
34
+ `assertive: false` and a trimmed `message`; a message of only whitespace is not
35
+ announced.
36
+
37
+ ### Fixed
38
+
39
+ - announcer: region timers are released on `disconnect()` and before a Turbo
40
+ snapshot, so an announcement made after reconnecting can no longer cancel the
41
+ auto-clear of a different live region.
42
+
10
43
  ## [0.14.0] - 2026-09-19
11
44
 
12
45
  Minor release with no new components. input-mask changes what a broken `tokens`
@@ -1068,6 +1101,7 @@ Initial public alpha: 101 behavior-only, accessible Stimulus controllers driven
1068
1101
  by `data-*` attributes, shipping no CSS. Published to npm (with provenance) and
1069
1102
  RubyGems.
1070
1103
 
1104
+ [0.15.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.15.0
1071
1105
  [0.14.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.14.0
1072
1106
  [0.13.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.13.0
1073
1107
  [0.12.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.12.0
@@ -530,6 +530,51 @@ var LiveCounterController = class _LiveCounterController extends Controller {
530
530
  }, _LiveCounterController.#announceDelay);
531
531
  }
532
532
  };
533
+
534
+ // src/utils/keyed_timers.ts
535
+ var KeyedTimers = class {
536
+ #timers = new SafeTimeout();
537
+ /** The pending timer of each key; an entry lives exactly as long as its timer. */
538
+ #pending = /* @__PURE__ */ new Map();
539
+ /**
540
+ * Arms `callback` after `delay` ms for `key`, cancelling the timer `key` had
541
+ * pending. The entry is dropped before the callback runs, so the callback sees
542
+ * the key unarmed and may arm it again for the next round.
543
+ */
544
+ set(key, callback, delay) {
545
+ this.clear(key);
546
+ const id = this.#timers.set(() => {
547
+ this.#pending.delete(key);
548
+ callback();
549
+ }, delay);
550
+ this.#pending.set(key, id);
551
+ }
552
+ /**
553
+ * Cancels `key`'s pending timer, if it has one.
554
+ *
555
+ * A timer id is a positive integer, so `-1` stands for "nothing pending" and
556
+ * the registry ignores an id it does not own — the unarmed case needs no
557
+ * branch of its own, and no other key's timer can be reached from here.
558
+ */
559
+ clear(key) {
560
+ this.#timers.clear(this.#pending.get(key) ?? -1);
561
+ this.#pending.delete(key);
562
+ }
563
+ /**
564
+ * Cancels every pending timer and forgets every key. Call this from a
565
+ * controller's `disconnect()` so no timer, and no entry, outlives the element.
566
+ */
567
+ clearAll() {
568
+ this.#timers.clearAll();
569
+ this.#pending.clear();
570
+ }
571
+ /** Whether `key` has a timer pending. */
572
+ has(key) {
573
+ return this.#pending.has(key);
574
+ }
575
+ };
576
+
577
+ // src/cable/presence_controller.ts
533
578
  var BEACON_THROTTLE_MS = 2e3;
534
579
  var DEFAULT_HEARTBEAT_MS = 15e3;
535
580
  var DEFAULT_TIMEOUT_MS = 4e4;
@@ -554,6 +599,8 @@ var PresenceController = class _PresenceController extends Controller {
554
599
  #subscription = null;
555
600
  /** Present peers keyed by id (insertion order = join order). */
556
601
  #peers = /* @__PURE__ */ new Map();
602
+ /** Each peer's auto-expiry timer, restarted by every beacon from that peer. */
603
+ #expiry = new KeyedTimers();
557
604
  #timers = new SafeTimeout();
558
605
  #intervals = new SafeInterval();
559
606
  /** Epoch ms of the last outgoing beacon, for the convergence throttle. */
@@ -739,9 +786,8 @@ ${this.idValue}`;
739
786
  }
740
787
  const name = typeof beacon?.name === "string" ? beacon.name : "";
741
788
  const existing = this.#peers.get(id);
742
- if (existing !== void 0) this.#timers.clear(existing.timer);
743
- const timer = this.#timers.set(() => this.#drop(id), this.#timeout);
744
- this.#peers.set(id, { name, timer });
789
+ this.#expiry.set(id, () => this.#drop(id), this.#timeout);
790
+ this.#peers.set(id, { name });
745
791
  if (existing === void 0) {
746
792
  this.#appendClone(id, name);
747
793
  this.#render();
@@ -757,7 +803,7 @@ ${this.idValue}`;
757
803
  #drop(id) {
758
804
  const peer = this.#peers.get(id);
759
805
  if (peer === void 0) return;
760
- this.#timers.clear(peer.timer);
806
+ this.#expiry.clear(id);
761
807
  this.#peers.delete(id);
762
808
  this.#removeClone(id);
763
809
  this.#render();
@@ -834,6 +880,7 @@ ${this.idValue}`;
834
880
  /** Clears the transient roster state (connect reset + disconnect teardown). */
835
881
  #reset() {
836
882
  this.#timers.clearAll();
883
+ this.#expiry.clearAll();
837
884
  this.#pendingBeacon = null;
838
885
  this.#announceId = null;
839
886
  this.#peers.clear();
@@ -871,8 +918,10 @@ var TypingIndicatorController = class _TypingIndicatorController extends Control
871
918
  /** Identifier parameters parsed once from their declaration, never in the hot path. */
872
919
  #params = {};
873
920
  #subscription = null;
874
- /** Names currently typing (other clients), each with its auto-clear timer id. */
875
- #typers = /* @__PURE__ */ new Map();
921
+ /** Names currently typing (other clients), in the order their first signal arrived. */
922
+ #typers = /* @__PURE__ */ new Set();
923
+ /** Each typer's auto-clear timer, restarted by every further signal from that name. */
924
+ #expiry = new KeyedTimers();
876
925
  #timers = new SafeTimeout();
877
926
  /** Epoch ms of the last broadcast, for leading-edge throttling. */
878
927
  #lastSentAt = 0;
@@ -922,6 +971,7 @@ var TypingIndicatorController = class _TypingIndicatorController extends Control
922
971
  this.#subscription?.unsubscribe();
923
972
  this.#subscription = null;
924
973
  this.#timers.clearAll();
974
+ this.#expiry.clearAll();
925
975
  this.#announceId = null;
926
976
  this.#typers.clear();
927
977
  this.element.removeAttribute("data-typing");
@@ -945,13 +995,9 @@ var TypingIndicatorController = class _TypingIndicatorController extends Control
945
995
  #onReceived(data) {
946
996
  const name = data?.name;
947
997
  if (typeof name !== "string" || name === "" || name === this.nameValue) return;
948
- const existing = this.#typers.get(name);
949
- if (existing !== void 0) this.#timers.clear(existing);
950
- const added = existing === void 0;
951
- this.#typers.set(
952
- name,
953
- this.#timers.set(() => this.#untrack(name), this.#timeout)
954
- );
998
+ const added = !this.#typers.has(name);
999
+ this.#typers.add(name);
1000
+ this.#expiry.set(name, () => this.#untrack(name), this.#timeout);
955
1001
  if (added) this.#render();
956
1002
  }
957
1003
  /**
@@ -1011,7 +1057,7 @@ var TypingIndicatorController = class _TypingIndicatorController extends Control
1011
1057
  }
1012
1058
  /** Writes the current typer set onto the hook and the status slot. */
1013
1059
  #paint() {
1014
- const names = [...this.#typers.keys()];
1060
+ const names = [...this.#typers];
1015
1061
  this.element.setAttribute("data-typing", names.length > 0 ? "true" : "false");
1016
1062
  if (this.hasStatusTarget) {
1017
1063
  this.statusTarget.textContent = this.#message(names);