stimeo-ui 0.4.0 → 0.5.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 (123) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/controllers/announcer_controller.js.map +1 -1
  3. package/dist/controllers/aspect_ratio_controller.d.ts +2 -2
  4. package/dist/controllers/aspect_ratio_controller.js +1 -1
  5. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  6. package/dist/controllers/breadcrumb_controller.js +5 -1
  7. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  8. package/dist/controllers/carousel_controller.js +5 -1
  9. package/dist/controllers/carousel_controller.js.map +1 -1
  10. package/dist/controllers/clipboard_controller.js +8 -3
  11. package/dist/controllers/clipboard_controller.js.map +1 -1
  12. package/dist/controllers/collapsible_controller.d.ts +1 -1
  13. package/dist/controllers/collapsible_controller.js +4 -1
  14. package/dist/controllers/collapsible_controller.js.map +1 -1
  15. package/dist/controllers/color_picker_controller.d.ts +2 -2
  16. package/dist/controllers/color_picker_controller.js +6 -2
  17. package/dist/controllers/color_picker_controller.js.map +1 -1
  18. package/dist/controllers/combobox_controller.js.map +1 -1
  19. package/dist/controllers/command_palette_controller.js.map +1 -1
  20. package/dist/controllers/context_menu_controller.d.ts +1 -1
  21. package/dist/controllers/context_menu_controller.js +2 -2
  22. package/dist/controllers/context_menu_controller.js.map +1 -1
  23. package/dist/controllers/countdown_controller.d.ts +5 -3
  24. package/dist/controllers/countdown_controller.js +5 -1
  25. package/dist/controllers/countdown_controller.js.map +1 -1
  26. package/dist/controllers/date_range_picker_controller.js +5 -1
  27. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  28. package/dist/controllers/direct_upload_controller.d.ts +1 -1
  29. package/dist/controllers/direct_upload_controller.js +3 -3
  30. package/dist/controllers/direct_upload_controller.js.map +1 -1
  31. package/dist/controllers/empty_state_controller.d.ts +27 -7
  32. package/dist/controllers/empty_state_controller.js +107 -16
  33. package/dist/controllers/empty_state_controller.js.map +1 -1
  34. package/dist/controllers/flash_controller.d.ts +25 -5
  35. package/dist/controllers/flash_controller.js +161 -21
  36. package/dist/controllers/flash_controller.js.map +1 -1
  37. package/dist/controllers/form_validation_controller.js +8 -2
  38. package/dist/controllers/form_validation_controller.js.map +1 -1
  39. package/dist/controllers/frame_loading_controller.d.ts +19 -2
  40. package/dist/controllers/frame_loading_controller.js +94 -22
  41. package/dist/controllers/frame_loading_controller.js.map +1 -1
  42. package/dist/controllers/highlight_controller.d.ts +8 -4
  43. package/dist/controllers/highlight_controller.js +38 -1
  44. package/dist/controllers/highlight_controller.js.map +1 -1
  45. package/dist/controllers/idle_controller.d.ts +2 -1
  46. package/dist/controllers/idle_controller.js +13 -2
  47. package/dist/controllers/idle_controller.js.map +1 -1
  48. package/dist/controllers/listbox_controller.js.map +1 -1
  49. package/dist/controllers/local_time_controller.js +2 -0
  50. package/dist/controllers/local_time_controller.js.map +1 -1
  51. package/dist/controllers/masonry_controller.d.ts +1 -1
  52. package/dist/controllers/masonry_controller.js +1 -1
  53. package/dist/controllers/masonry_controller.js.map +1 -1
  54. package/dist/controllers/meter_controller.js +3 -1
  55. package/dist/controllers/meter_controller.js.map +1 -1
  56. package/dist/controllers/multi_select_controller.js.map +1 -1
  57. package/dist/controllers/network_status_controller.d.ts +15 -13
  58. package/dist/controllers/network_status_controller.js +1 -3
  59. package/dist/controllers/network_status_controller.js.map +1 -1
  60. package/dist/controllers/number_input_controller.d.ts +8 -0
  61. package/dist/controllers/number_input_controller.js +191 -24
  62. package/dist/controllers/number_input_controller.js.map +1 -1
  63. package/dist/controllers/overflow_menu_controller.js +1 -1
  64. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  65. package/dist/controllers/pagination_controller.js +5 -1
  66. package/dist/controllers/pagination_controller.js.map +1 -1
  67. package/dist/controllers/password_strength_controller.d.ts +1 -1
  68. package/dist/controllers/password_strength_controller.js +1 -1
  69. package/dist/controllers/password_strength_controller.js.map +1 -1
  70. package/dist/controllers/pointer_drag_controller.js +10 -0
  71. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  72. package/dist/controllers/portal_controller.js +10 -0
  73. package/dist/controllers/portal_controller.js.map +1 -1
  74. package/dist/controllers/progress_controller.d.ts +1 -1
  75. package/dist/controllers/progress_controller.js +7 -3
  76. package/dist/controllers/progress_controller.js.map +1 -1
  77. package/dist/controllers/range_slider_controller.d.ts +23 -0
  78. package/dist/controllers/range_slider_controller.js +385 -94
  79. package/dist/controllers/range_slider_controller.js.map +1 -1
  80. package/dist/controllers/rating_controller.js +2 -0
  81. package/dist/controllers/rating_controller.js.map +1 -1
  82. package/dist/controllers/relative_time_controller.js +2 -0
  83. package/dist/controllers/relative_time_controller.js.map +1 -1
  84. package/dist/controllers/scroll_area_controller.d.ts +1 -1
  85. package/dist/controllers/scroll_area_controller.js +1 -1
  86. package/dist/controllers/scroll_area_controller.js.map +1 -1
  87. package/dist/controllers/separator_controller.js +13 -17
  88. package/dist/controllers/separator_controller.js.map +1 -1
  89. package/dist/controllers/skeleton_controller.d.ts +2 -2
  90. package/dist/controllers/skeleton_controller.js +71 -3
  91. package/dist/controllers/skeleton_controller.js.map +1 -1
  92. package/dist/controllers/slider_controller.d.ts +17 -1
  93. package/dist/controllers/slider_controller.js +325 -48
  94. package/dist/controllers/slider_controller.js.map +1 -1
  95. package/dist/controllers/spinner_controller.d.ts +15 -10
  96. package/dist/controllers/spinner_controller.js +18 -3
  97. package/dist/controllers/spinner_controller.js.map +1 -1
  98. package/dist/controllers/step_indicator_controller.d.ts +1 -1
  99. package/dist/controllers/step_indicator_controller.js +3 -1
  100. package/dist/controllers/step_indicator_controller.js.map +1 -1
  101. package/dist/controllers/stepper_controller.js +2 -0
  102. package/dist/controllers/stepper_controller.js.map +1 -1
  103. package/dist/controllers/switch_controller.d.ts +12 -8
  104. package/dist/controllers/switch_controller.js +162 -18
  105. package/dist/controllers/switch_controller.js.map +1 -1
  106. package/dist/controllers/textarea_autosize_controller.js +1 -1
  107. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  108. package/dist/controllers/time_picker_controller.d.ts +3 -0
  109. package/dist/controllers/time_picker_controller.js +6 -3
  110. package/dist/controllers/time_picker_controller.js.map +1 -1
  111. package/dist/controllers/tree_view_controller.d.ts +1 -2
  112. package/dist/controllers/tree_view_controller.js +19 -1
  113. package/dist/controllers/tree_view_controller.js.map +1 -1
  114. package/dist/index.js +1214 -307
  115. package/dist/index.js.map +1 -1
  116. package/dist/inspector/cli.d.ts +76 -1
  117. package/dist/inspector/cli.js +152 -0
  118. package/dist/inspector/cli.js.map +1 -1
  119. package/dist/inspector/cli_bin.js +212 -2
  120. package/dist/inspector/cli_bin.js.map +1 -1
  121. package/dist/inspector/examples.json +17 -17
  122. package/dist/inspector/manifest.json +408 -26
  123. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/range.ts","../../src/controllers/range_slider_controller.ts"],"names":[],"mappings":";;;;;AAiBO,SAAS,MAAM,OAAA,EAA2B;AAC/C,EAAA,OAAO,MAAA,CAAO,gBAAA,CAAiB,OAAO,CAAA,CAAE,SAAA,KAAc,KAAA;AACxD;;;AC6CO,SAAS,eAAA,CAAgB,KAAa,OAAA,EAA0B;AACrE,EAAA,IAAI,GAAA,KAAQ,YAAA,IAAgB,GAAA,KAAQ,WAAA,EAAa,OAAO,GAAA;AACxD,EAAA,IAAI,CAAC,KAAA,CAAM,OAAO,CAAA,EAAG,OAAO,GAAA;AAC5B,EAAA,OAAO,GAAA,KAAQ,eAAe,WAAA,GAAc,YAAA;AAC9C;AA2BO,SAAS,oBAAA,CACd,KAAA,EACA,KAAA,GAAkC,EAAC,EAC1B;AACT,EAAA,IAAI,CAAC,KAAA,CAAM,GAAA,CAAI,UAAA,CAAW,OAAO,GAAG,OAAO,KAAA;AAC3C,EAAA,OACG,KAAA,CAAM,MAAA,IAAU,CAAC,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA,IACrC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,QAAA,CAAS,MAAM,KACvC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,IACvC,KAAA,CAAM,QAAA,IAAY,CAAC,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA;AAE9C;;;ACvDO,IAAM,qBAAN,MAAyB;AAAA,EACrB,IAAA;AAAA,EACT,OAAA,GAAU,KAAA;AAAA,EACV,OAAA,GAAU,KAAA;AAAA,EACV,WAAA,GAAc,CAAA;AAAA;AAAA,EAGd,YAAY,GAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,EACjB;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,WAAA,IAAe,CAAA;AAAA,EACtB;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,EAAS;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,cAAA,CAAe,MAAM;AAEnB,MAAA,IAAI,UAAA,KAAe,KAAK,WAAA,IAAe,CAAC,KAAK,OAAA,IAAW,CAAC,KAAK,OAAA,EAAS;AACvE,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;;AC9DO,SAAS,aAAA,CAAc,KAAA,EAAe,GAAA,EAAa,GAAA,EAAqB;AAC7E,EAAA,MAAM,OAAO,GAAA,GAAM,GAAA;AACnB,EAAA,IAAI,EAAE,IAAA,GAAO,CAAA,CAAA,EAAI,OAAO,CAAA;AAExB,EAAA,MAAM,OAAA,GAAU,KAAK,GAAA,CAAI,GAAA,EAAK,KAAK,GAAA,CAAI,GAAA,EAAK,KAAK,CAAC,CAAA;AAClD,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG;AACzB,IAAA,QAAA,GAAA,CAAY,UAAU,GAAA,IAAO,IAAA;AAAA,EAC/B,CAAA,MAAO;AAGL,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,GAAG,CAAC,CAAA;AACnD,IAAA,QAAA,GAAA,CAAY,UAAU,KAAA,GAAQ,GAAA,GAAM,KAAA,KAAU,GAAA,GAAM,QAAQ,GAAA,GAAM,KAAA,CAAA;AAAA,EACpE;AAEA,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,QAAQ,GAAG,OAAO,CAAA;AACvC,EAAA,OAAO,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,QAAQ,CAAC,CAAA;AAC1C;;;AClCA,IAAM,cAAA,GAAiB,sBAAA;AACvB,IAAM,YAAA,GAAe,oBAAA;AA2Cd,IAAM,qBAAA,GAAN,cAAoC,UAAA,CAAwB;AAAA,EACjE,OAAgB,OAAA,GAAU,CAAC,OAAA,EAAS,cAAc,UAAU,CAAA;AAAA,EAC5D,OAAgB,MAAA,GAAS;AAAA,IACvB,GAAA,EAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,CAAA,EAAE;AAAA,IAChC,GAAA,EAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,GAAA,EAAI;AAAA,IAClC,IAAA,EAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,CAAA,EAAE;AAAA,IACjC,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,CAAA,EAAE;AAAA,IAClC,GAAA,EAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,GAAA,EAAI;AAAA,IAClC,YAAA,EAAc,EAAE,IAAA,EAAM,OAAA,EAAS,SAAS,KAAA;AAAM,GAChD;AAAA,EACA,OAAO,OAAA,GAAU,CAAC,WAAA,EAAa,eAAe,CAAA;AAAA,EAC9C,OAAO,MAAA,GAAS,CAAC,QAAQ,CAAA;AAAA;AAAA,EAgBzB,UAAA,GAAqC,IAAA;AAAA;AAAA,EAGrC,IAAI,SAAA,GAAqB;AACvB,IAAA,OAAO,IAAA,CAAK,iBAAA,IAAqB,KAAA,CAAM,IAAA,CAAK,OAAO,CAAA;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOS,QAAA,GAAW,IAAI,kBAAA,CAAmB,MAAM;AAC/C,IAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,QAAQ,CAAA;AAAA,EAC7C,CAAC,CAAA;AAAA,EAEQ,OAAA,GAAgB;AACvB,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAGvB,IAAA,MAAM,KAAK,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,UAAA,EAAY,KAAK,QAAQ,CAAA;AAClD,IAAA,MAAM,KAAK,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,UAAA,EAAY,KAAK,QAAQ,CAAA;AAClD,IAAA,IAAA,CAAK,OAAA,CAAQ,EAAA,EAAI,EAAA,EAAI,KAAK,CAAA;AAAA,EAC5B;AAAA;AAAA,EAGS,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,SAAS,MAAA,EAAO;AACrB,IAAA,IAAA,CAAK,YAAY,KAAA,EAAM;AACvB,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAAA,EACpB;AAAA;AAAA,EAGA,eAAA,GAAwB;AACtB,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,eAAA,GAAwB;AACtB,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,UAAU,KAAA,EAA4B;AACpC,IAAA,IAAI,oBAAA,CAAqB,KAAK,CAAA,EAAG;AACjC,IAAA,MAAM,QAAQ,KAAA,CAAM,aAAA;AACpB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,mBAAA,IAAuB,KAAA,KAAU,IAAA,CAAK,gBAAA;AAC3D,IAAA,MAAM,OAAA,GAAU,OAAA,GAAU,IAAA,CAAK,UAAA,GAAa,IAAA,CAAK,QAAA;AAEjD,IAAA,MAAM,KAAA,GAAQ,OAAA,GAAU,IAAA,CAAK,QAAA,GAAW,IAAA,CAAK,UAAA;AAC7C,IAAA,MAAM,KAAA,GAAQ,OAAA,GAAU,IAAA,CAAK,QAAA,GAAW,IAAA,CAAK,QAAA;AAC7C,IAAA,MAAM,GAAA,GAAM,KAAK,SAAA,GAAY,EAAA;AAE7B,IAAA,IAAI,IAAA,GAAsB,IAAA;AAG1B,IAAA,QAAQ,IAAA,CAAK,YAAY,eAAA,CAAgB,KAAA,CAAM,KAAK,IAAA,CAAK,OAAO,CAAA,GAAI,KAAA,CAAM,GAAA;AAAK,MAC7E,KAAK,YAAA;AAAA,MACL,KAAK,SAAA;AACH,QAAA,IAAA,GAAO,UAAU,IAAA,CAAK,SAAA;AACtB,QAAA;AAAA,MACF,KAAK,WAAA;AAAA,MACL,KAAK,WAAA;AACH,QAAA,IAAA,GAAO,UAAU,IAAA,CAAK,SAAA;AACtB,QAAA;AAAA,MACF,KAAK,QAAA;AACH,QAAA,IAAA,GAAO,OAAA,GAAU,GAAA;AACjB,QAAA;AAAA,MACF,KAAK,UAAA;AACH,QAAA,IAAA,GAAO,OAAA,GAAU,GAAA;AACjB,QAAA;AAAA,MACF,KAAK,MAAA;AACH,QAAA,IAAA,GAAO,KAAA;AACP,QAAA;AAAA,MACF,KAAK,KAAA;AACH,QAAA,IAAA,GAAO,KAAA;AACP,QAAA;AAAA,MACF;AACE,QAAA;AAAA;AAEJ,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,IAAA,CAAK,UAAA,CAAW,SAAS,IAAI,CAAA;AAAA,EAC/B;AAAA;AAAA,EAGA,cAAc,KAAA,EAA2B;AACvC,IAAA,IAAI,CAAC,KAAK,cAAA,EAAgB;AAI1B,IAAA,MAAM,WAAW,IAAA,CAAK,SAAA;AACtB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,iBAAA,CAAkB,KAAA,CAAM,SAAS,QAAQ,CAAA;AAC5D,IAAA,IAAI,UAAU,IAAA,EAAM;AACpB,IAAA,KAAA,CAAM,cAAA,EAAe;AAKrB,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,IAAA,CAAK,UAAU,CAAA,IAAK,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,IAAA,CAAK,QAAQ,CAAA;AACpF,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,IAAI,IAAA,CAAK,mBAAA,EAAqB,IAAA,CAAK,gBAAA,CAAiB,KAAA,EAAM;AAAA,IAC5D,CAAA,MAAA,IAAW,KAAK,iBAAA,EAAmB;AACjC,MAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAAA,IAC5B;AACA,IAAA,IAAA,CAAK,UAAA,CAAW,UAAU,KAAK,CAAA;AAE/B,IAAA,IAAA,CAAK,YAAY,KAAA,EAAM;AACvB,IAAA,MAAM,KAAA,GAAQ,IAAI,eAAA,EAAgB;AAClC,IAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAClB,IAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KAA6B;AAC3C,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,iBAAA,CAAkB,IAAA,CAAK,SAAS,QAAQ,CAAA;AAC3D,MAAA,IAAI,KAAA,KAAU,IAAA,EAAM,IAAA,CAAK,UAAA,CAAW,UAAU,KAAK,CAAA;AAAA,IACrD,CAAA;AACA,IAAA,MAAM,OAAO,MAAY;AACvB,MAAA,KAAA,CAAM,KAAA,EAAM;AACZ,MAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAAA,IACpB,CAAA;AACA,IAAA,QAAA,CAAS,iBAAiB,aAAA,EAAe,MAAA,EAAQ,EAAE,MAAA,EAAQ,KAAA,CAAM,QAAQ,CAAA;AACzE,IAAA,QAAA,CAAS,iBAAiB,WAAA,EAAa,IAAA,EAAM,EAAE,MAAA,EAAQ,KAAA,CAAM,QAAQ,CAAA;AAGrE,IAAA,QAAA,CAAS,iBAAiB,eAAA,EAAiB,IAAA,EAAM,EAAE,MAAA,EAAQ,KAAA,CAAM,QAAQ,CAAA;AAAA,EAC3E;AAAA;AAAA,EAGA,iBAAA,CAAkB,SAAiB,QAAA,EAAkC;AACnE,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,qBAAA,EAAsB;AACpD,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,CAAA,EAAG,OAAO,IAAA;AAC7B,IAAA,MAAM,MAAA,GAAA,CAAU,OAAA,GAAU,IAAA,CAAK,IAAA,IAAQ,IAAA,CAAK,KAAA;AAC5C,IAAA,MAAM,QAAA,GAAW,QAAA,GAAW,CAAA,GAAI,MAAA,GAAS,MAAA;AACzC,IAAA,OAAO,IAAA,CAAK,QAAA,GAAW,QAAA,IAAY,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA,CAAA;AAAA,EAC1D;AAAA;AAAA,EAGA,UAAA,CAAW,SAAkB,GAAA,EAAmB;AAC9C,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,IAAA,CAAK,QAAA,EAAU,IAAI,CAAA;AAAA,IACvC,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,UAAA,EAAY,GAAA,EAAK,IAAI,CAAA;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAA,CAAQ,KAAA,EAAe,GAAA,EAAa,MAAA,EAAuB;AACzD,IAAA,MAAM,YAAY,IAAA,CAAK,UAAA;AACvB,IAAA,MAAM,UAAU,IAAA,CAAK,QAAA;AAErB,IAAA,IAAI,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAChC,IAAA,IAAI,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAE5B,IAAA,IAAI,YAAY,OAAA,EAAS;AACvB,MAAA,IAAI,kBAAkB,KAAA,EAAO,SAAA,EAAW,GAAA,EAAK,OAAO,GAAG,SAAA,GAAY,OAAA;AAAA,WAC9D,OAAA,GAAU,SAAA;AAAA,IACjB;AAEA,IAAA,IAAA,CAAK,UAAA,GAAa,SAAA;AAClB,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAChB,IAAA,IAAA,CAAK,OAAA,CAAQ,WAAW,OAAO,CAAA;AAE/B,IAAA,IAAI,MAAA,KAAW,SAAA,KAAc,SAAA,IAAa,OAAA,KAAY,OAAA,CAAA,EAAU;AAC9D,MAAA,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,EAAE,MAAA,EAAQ,EAAE,OAAO,SAAA,EAAW,GAAA,EAAK,OAAA,EAAQ,EAAG,CAAA;AAAA,IACxE;AAAA,EACF;AAAA;AAAA,EAGA,OAAA,CAAQ,OAAe,GAAA,EAAmB;AACxC,IAAA,IAAI,KAAK,mBAAA,EAAqB;AAC5B,MAAA,IAAA,CAAK,iBAAiB,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAC,CAAA;AACzE,MAAA,IAAA,CAAK,gBAAA,CAAiB,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,GAAG,CAAC,CAAA;AAC/D,MAAA,IAAA,CAAK,gBAAA,CAAiB,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IACnE;AACA,IAAA,IAAI,KAAK,iBAAA,EAAmB;AAC1B,MAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,KAAK,CAAC,CAAA;AAC/D,MAAA,IAAA,CAAK,eAAe,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAC,CAAA;AACvE,MAAA,IAAA,CAAK,cAAA,CAAe,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,IAC/D;AACA,IAAA,IAAA,CAAK,QAAQ,KAAA,CAAM,WAAA;AAAA,MACjB,cAAA;AAAA,MACA,OAAO,aAAA,CAAc,KAAA,EAAO,KAAK,QAAA,EAAU,IAAA,CAAK,QAAQ,CAAC;AAAA,KAC3D;AACA,IAAA,IAAA,CAAK,QAAQ,KAAA,CAAM,WAAA;AAAA,MACjB,YAAA;AAAA,MACA,OAAO,aAAA,CAAc,GAAA,EAAK,KAAK,QAAA,EAAU,IAAA,CAAK,QAAQ,CAAC;AAAA,KACzD;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,GAAA,EAAqB;AACzB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,QAAA,EAAU,KAAK,GAAA,CAAI,IAAA,CAAK,QAAA,EAAU,GAAG,CAAC,CAAA;AACpE,IAAA,IAAI,IAAA,CAAK,SAAA,IAAa,CAAA,EAAG,OAAO,OAAA;AAChC,IAAA,MAAM,OAAA,GACJ,IAAA,CAAK,KAAA,CAAA,CAAO,OAAA,GAAU,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,SAAS,CAAA,GAAI,IAAA,CAAK,SAAA,GAAY,IAAA,CAAK,QAAA;AACjF,IAAA,OAAO,IAAA,CAAK,IAAI,IAAA,CAAK,QAAA,EAAU,KAAK,GAAA,CAAI,IAAA,CAAK,QAAA,EAAU,OAAO,CAAC,CAAA;AAAA,EACjE;AACF;AAOA,SAAS,iBAAA,CACP,KAAA,EACA,SAAA,EACA,GAAA,EACA,OAAA,EACS;AACT,EAAA,OAAO,KAAA,KAAU,aAAa,GAAA,KAAQ,OAAA;AACxC","file":"range_slider_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Collapses many target callbacks from one DOM mutation into a single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element, so\n * replacing a list of N options delivers N callbacks — but the useful unit of\n * work is \"reconcile against the DOM that resulted\", once, after the batch has\n * settled. Every controller that owns a reconcilable target set needs the same\n * shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers the *initial* target callbacks\n * ahead of `connect()`. Reconciling there would compute a fallback against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Range normalization shared by the value-bearing controllers (progress, meter,\n * slider, range-slider).\n *\n * Each of them publishes \"where the value sits inside `[min, max]`\" as a CSS\n * custom property the consumer multiplies a track by, and each has to answer the\n * same degenerate cases: empty and inverted ranges cannot express progress, and\n * malformed Number Values can produce `NaN`. Keeping the rule in one place is\n * what makes those answers identical across the four.\n */\n\n/**\n * Fraction of `[min, max]` that `value` occupies, always within `[0, 1]`.\n *\n * `value` is clamped into the range first, so a value outside it reports a full\n * or empty track rather than pushing the fraction past the ends.\n *\n * An empty, inverted, or non-numeric range yields `0`. That is a deliberate\n * floor rather than a computed result: `min === max` would produce `NaN`, and\n * `min > max` would report a full track for a value that is really out of range.\n * Non-finite results are floored for the same reason. These results reach\n * assistive tech, because the same fraction drives the percentage substituted\n * into `aria-valuetext`.\n */\nexport function rangeFraction(value: number, min: number, max: number): number {\n const span = max - min;\n if (!(span > 0)) return 0;\n\n const clamped = Math.min(max, Math.max(min, value));\n let fraction: number;\n if (Number.isFinite(span)) {\n fraction = (clamped - min) / span;\n } else {\n // Scaling preserves the ratio when two finite endpoints straddle zero so\n // widely that their subtraction overflows to Infinity.\n const scale = Math.max(Math.abs(min), Math.abs(max));\n fraction = (clamped / scale - min / scale) / (max / scale - min / scale);\n }\n\n if (!Number.isFinite(fraction)) return 0;\n return Math.min(1, Math.max(0, fraction));\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { rangeFraction } from \"../utils/range\";\n\n/** CSS custom properties exposing each thumb's position (0..1) to the consumer. */\nconst START_PROPERTY = \"--stimeo-range-start\";\nconst END_PROPERTY = \"--stimeo-range-end\";\n\n/**\n * Headless, accessible **two-thumb range slider** behavior (APG Slider —\n * Multi-Thumb). A derivative of {@link SliderController}: it manages two thumbs\n * (`start` ≤ `end`) that constrain each other.\n *\n * Markup contract (identifier: `stimeo--range-slider`):\n * <div data-controller=\"stimeo--range-slider\"\n * data-stimeo--range-slider-min-value=\"0\"\n * data-stimeo--range-slider-max-value=\"100\"\n * data-stimeo--range-slider-step-value=\"1\"\n * data-stimeo--range-slider-start-value=\"20\"\n * data-stimeo--range-slider-end-value=\"80\">\n * <div data-stimeo--range-slider-target=\"track\"\n * data-action=\"pointerdown->stimeo--range-slider#onPointerDown\">\n * <div role=\"slider\" tabindex=\"0\" aria-label=\"Minimum\"\n * data-stimeo--range-slider-target=\"startThumb\"\n * data-action=\"keydown->stimeo--range-slider#onKeydown\"></div>\n * <div role=\"slider\" tabindex=\"0\" aria-label=\"Maximum\"\n * data-stimeo--range-slider-target=\"endThumb\"\n * data-action=\"keydown->stimeo--range-slider#onKeydown\"></div>\n * </div>\n * </div>\n *\n * @remarks\n * Behavior only — the consumer owns all layout (positioning the thumbs and the\n * selected range from the fractions). Only the horizontal orientation is handled.\n * Each thumb's movable range is bounded by the *other* thumb's current value,\n * reflected on its `aria-valuemin`/`aria-valuemax` so assistive tech announces the\n * live constraint.\n *\n * Behavior provided (per focused thumb):\n * - `ArrowRight`/`ArrowUp` increase and `ArrowLeft`/`ArrowDown` decrease by one\n * step; `Home`/`End` jump to that thumb's movable min/max; `PageUp`/`PageDown`\n * move by ten steps. A thumb never crosses the other.\n * - Pointer press/drag on the track moves the nearest thumb.\n *\n * The fractions are value ratios, not positions, so only the consumer knows\n * whether their track mirrors under RTL. Set `logicalTrack` to declare that it\n * does: the pointer mapping and the horizontal arrow pair then follow the\n * writing direction. Left unset, nothing here reads `direction`.\n */\nexport class RangeSliderController extends Controller<HTMLElement> {\n static override targets = [\"track\", \"startThumb\", \"endThumb\"];\n static override values = {\n min: { type: Number, default: 0 },\n max: { type: Number, default: 100 },\n step: { type: Number, default: 1 },\n start: { type: Number, default: 0 },\n end: { type: Number, default: 100 },\n logicalTrack: { type: Boolean, default: false },\n };\n static actions = [\"onKeydown\", \"onPointerDown\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly trackTarget: HTMLElement;\n declare readonly startThumbTarget: HTMLElement;\n declare readonly endThumbTarget: HTMLElement;\n declare readonly hasTrackTarget: boolean;\n declare readonly hasStartThumbTarget: boolean;\n declare readonly hasEndThumbTarget: boolean;\n declare minValue: number;\n declare maxValue: number;\n declare stepValue: number;\n declare startValue: number;\n declare endValue: number;\n declare logicalTrackValue: boolean;\n\n /** Aborts in-progress pointer-drag listeners when the drag ends or on teardown. */\n #dragAbort: AbortController | null = null;\n\n /** Whether the consumer declared a mirroring track and the direction mirrors it. */\n get #mirrored(): boolean {\n return this.logicalTrackValue && isRtl(this.element);\n }\n\n /** Normalizes the initial pair (clamped, snapped, ordered) and renders. */\n /**\n * Collapses a morph that swaps render inputs into one repaint, and refuses the\n * pass Stimulus delivers before `connect()`.\n */\n readonly #repaint = new MicrotaskCoalescer(() => {\n this.#render(this.startValue, this.endValue);\n });\n\n override connect(): void {\n this.#repaint.activate();\n // Order the initial pair up front so a reversed start/end is corrected by\n // swapping (preserving both values) rather than collapsing one onto the other.\n const lo = Math.min(this.startValue, this.endValue);\n const hi = Math.max(this.startValue, this.endValue);\n this.#commit(lo, hi, false);\n }\n\n /** Cancels any active pointer drag so document listeners never leak. */\n override disconnect(): void {\n this.#repaint.cancel();\n this.#dragAbort?.abort();\n this.#dragAbort = null;\n }\n\n /** Repaints when application code (or a Turbo morph) changes `min` at runtime. */\n minValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `max` at runtime. */\n maxValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Keyboard stepping for whichever thumb is focused (the action's element). */\n onKeydown(event: KeyboardEvent): void {\n if (isReservedArrowChord(event)) return;\n const thumb = event.currentTarget as HTMLElement;\n const isStart = this.hasStartThumbTarget && thumb === this.startThumbTarget;\n const current = isStart ? this.startValue : this.endValue;\n // The opposing thumb caps this one's travel so the two never cross.\n const lower = isStart ? this.minValue : this.startValue;\n const upper = isStart ? this.endValue : this.maxValue;\n const big = this.stepValue * 10;\n\n let next: number | null = null;\n // On a mirrored track the greater value sits at the visual left, so the\n // horizontal pair trades places; `Home`/`End` name bounds, not a direction.\n switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = current + this.stepValue;\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = current - this.stepValue;\n break;\n case \"PageUp\":\n next = current + big;\n break;\n case \"PageDown\":\n next = current - big;\n break;\n case \"Home\":\n next = lower;\n break;\n case \"End\":\n next = upper;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#moveThumb(isStart, next);\n }\n\n /** Begins a pointer drag on the track, moving the thumb nearest the press. */\n onPointerDown(event: PointerEvent): void {\n if (!this.hasTrackTarget) return;\n // Resolve the direction once for the whole gesture: reading it per move\n // would query computed style on every frame, and a drag that flipped\n // mid-gesture would be incoherent anyway.\n const mirrored = this.#mirrored;\n const value = this.#valueFromClientX(event.clientX, mirrored);\n if (value === null) return;\n event.preventDefault();\n\n // Pick the nearer thumb; ties go to the start thumb so a press exactly\n // between them stays deterministic. Only read the target getter behind its\n // has*Target guard — Stimulus throws when an absent target is accessed.\n const useStart = Math.abs(value - this.startValue) <= Math.abs(value - this.endValue);\n if (useStart) {\n if (this.hasStartThumbTarget) this.startThumbTarget.focus();\n } else if (this.hasEndThumbTarget) {\n this.endThumbTarget.focus();\n }\n this.#moveThumb(useStart, value);\n\n this.#dragAbort?.abort();\n const abort = new AbortController();\n this.#dragAbort = abort;\n const onMove = (move: PointerEvent): void => {\n const moved = this.#valueFromClientX(move.clientX, mirrored);\n if (moved !== null) this.#moveThumb(useStart, moved);\n };\n const onUp = (): void => {\n abort.abort();\n this.#dragAbort = null;\n };\n document.addEventListener(\"pointermove\", onMove, { signal: abort.signal });\n document.addEventListener(\"pointerup\", onUp, { signal: abort.signal });\n // pointercancel fires when the gesture is interrupted (OS gesture, scroll\n // takeover, device switch); clean up the same way so no listener leaks.\n document.addEventListener(\"pointercancel\", onUp, { signal: abort.signal });\n }\n\n /** Maps a pointer X coordinate to a raw value using the track geometry. */\n #valueFromClientX(clientX: number, mirrored: boolean): number | null {\n const rect = this.trackTarget.getBoundingClientRect();\n if (rect.width === 0) return null;\n const offset = (clientX - rect.left) / rect.width;\n const fraction = mirrored ? 1 - offset : offset;\n return this.minValue + fraction * (this.maxValue - this.minValue);\n }\n\n /** Moves one thumb to a new raw value, keeping the pair ordered. */\n #moveThumb(isStart: boolean, raw: number): void {\n if (isStart) {\n this.#commit(raw, this.endValue, true);\n } else {\n this.#commit(this.startValue, raw, true);\n }\n }\n\n /**\n * Clamps and snaps `start`/`end`, enforces `start ≤ end`, stores the pair, and\n * reflects it onto the thumbs' ARIA attributes and the range custom\n * properties. Dispatches `change` only on user-driven updates (`notify`).\n */\n #commit(start: number, end: number, notify: boolean): void {\n const prevStart = this.startValue;\n const prevEnd = this.endValue;\n\n let nextStart = this.#snap(start);\n let nextEnd = this.#snap(end);\n // Keep them ordered: a thumb pushed past its partner stops at the partner.\n if (nextStart > nextEnd) {\n if (isUserMovingStart(start, prevStart, end, prevEnd)) nextStart = nextEnd;\n else nextEnd = nextStart;\n }\n\n this.startValue = nextStart;\n this.endValue = nextEnd;\n this.#render(nextStart, nextEnd);\n\n if (notify && (nextStart !== prevStart || nextEnd !== prevEnd)) {\n this.dispatch(\"change\", { detail: { start: nextStart, end: nextEnd } });\n }\n }\n\n /** Reflects the current pair onto thumb ARIA attributes and CSS properties. */\n #render(start: number, end: number): void {\n if (this.hasStartThumbTarget) {\n this.startThumbTarget.setAttribute(\"aria-valuemin\", String(this.minValue));\n this.startThumbTarget.setAttribute(\"aria-valuemax\", String(end));\n this.startThumbTarget.setAttribute(\"aria-valuenow\", String(start));\n }\n if (this.hasEndThumbTarget) {\n this.endThumbTarget.setAttribute(\"aria-valuemin\", String(start));\n this.endThumbTarget.setAttribute(\"aria-valuemax\", String(this.maxValue));\n this.endThumbTarget.setAttribute(\"aria-valuenow\", String(end));\n }\n this.element.style.setProperty(\n START_PROPERTY,\n String(rangeFraction(start, this.minValue, this.maxValue)),\n );\n this.element.style.setProperty(\n END_PROPERTY,\n String(rangeFraction(end, this.minValue, this.maxValue)),\n );\n }\n\n /** Clamps `raw` to `[min, max]` and snaps it to the nearest step from `min`. */\n #snap(raw: number): number {\n const clamped = Math.min(this.maxValue, Math.max(this.minValue, raw));\n if (this.stepValue <= 0) return clamped;\n const stepped =\n Math.round((clamped - this.minValue) / this.stepValue) * this.stepValue + this.minValue;\n return Math.min(this.maxValue, Math.max(this.minValue, stepped));\n }\n}\n\n/**\n * Decides which thumb yields when a move would cross the pair. The moving thumb\n * is the one whose requested value differs from its previous value; it stops at\n * the partner rather than dragging the partner along.\n */\nfunction isUserMovingStart(\n start: number,\n prevStart: number,\n end: number,\n prevEnd: number,\n): boolean {\n return start !== prevStart && end === prevEnd;\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/owned_pointer_session.ts","../../src/utils/range.ts","../../src/utils/stepped_value.ts","../../src/controllers/range_slider_controller.ts"],"names":["position"],"mappings":"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","file":"range_slider_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/** Callbacks driven by one owned pointer until it ends or is cancelled. */\nexport interface OwnedPointerSessionHandlers {\n /** Receives movement from the initiating pointer only. */\n readonly move: (event: PointerEvent) => void;\n /** Runs once after every listener and pointer capture have been released. */\n readonly end?: () => void;\n}\n\n/**\n * Owns one pointer across document-level movement and termination events.\n *\n * A controller may have at most one instance at a time. The session filters\n * every event by the initiating `pointerId`, captures the pointer when the DOM\n * implementation supports it, and binds all fallback document listeners to one\n * abort signal. Calling {@link OwnedPointerSession.end} is idempotent.\n */\nexport class OwnedPointerSession {\n readonly pointerId: number;\n readonly #owner: HTMLElement;\n readonly #handlers: OwnedPointerSessionHandlers;\n readonly #abort = new AbortController();\n #active = true;\n\n constructor(start: PointerEvent, owner: HTMLElement, handlers: OwnedPointerSessionHandlers) {\n this.pointerId = start.pointerId;\n this.#owner = owner;\n this.#handlers = handlers;\n\n const { signal } = this.#abort;\n owner.ownerDocument.addEventListener(\"pointermove\", this.#onMove, { signal });\n owner.ownerDocument.addEventListener(\"pointerup\", this.#onEndEvent, { signal });\n owner.ownerDocument.addEventListener(\"pointercancel\", this.#onEndEvent, { signal });\n owner.addEventListener(\"lostpointercapture\", this.#onLostCapture, { signal });\n\n // Pointer capture is progressive here: happy-dom and older DOM shims may not\n // implement it, while the document listeners still preserve ownership.\n try {\n owner.setPointerCapture?.(this.pointerId);\n } catch {\n // A synthetic or already-ended pointer cannot be captured; filtering and\n // AbortController teardown remain sufficient for that environment.\n }\n }\n\n /** Whether this session still owns its pointer and listeners. */\n get active(): boolean {\n return this.#active;\n }\n\n /** Whether `event` belongs to the initiating pointer of the live session. */\n owns(event: PointerEvent): boolean {\n return this.#active && event.pointerId === this.pointerId;\n }\n\n /** Releases capture/listeners and invokes the end callback exactly once. */\n end(): void {\n if (!this.#active) return;\n this.#active = false;\n this.#abort.abort();\n try {\n this.#owner.releasePointerCapture?.(this.pointerId);\n } catch {\n // Losing capture because the owner detached is already an ended session.\n }\n this.#handlers.end?.();\n }\n\n readonly #onMove = (event: PointerEvent): void => {\n if (this.owns(event)) this.#handlers.move(event);\n };\n\n readonly #onEndEvent = (event: PointerEvent): void => {\n if (this.owns(event)) this.end();\n };\n\n readonly #onLostCapture = (event: Event): void => {\n const pointerId = (event as PointerEvent).pointerId;\n if (typeof pointerId === \"number\" && pointerId !== this.pointerId) return;\n this.end();\n };\n}\n","/**\n * Range normalization shared by the value-bearing controllers (progress, meter,\n * slider, range-slider).\n *\n * Each of them publishes \"where the value sits inside `[min, max]`\" as a CSS\n * custom property the consumer multiplies a track by, and each has to answer the\n * same degenerate cases: empty and inverted ranges cannot express progress, and\n * malformed Number Values can produce `NaN`. Keeping the rule in one place is\n * what makes those answers identical across the four.\n */\n\n/**\n * Fraction of `[min, max]` that `value` occupies, always within `[0, 1]`.\n *\n * `value` is clamped into the range first, so a value outside it reports a full\n * or empty track rather than pushing the fraction past the ends.\n *\n * An empty, inverted, or non-numeric range yields `0`. That is a deliberate\n * floor rather than a computed result: `min === max` would produce `NaN`, and\n * `min > max` would report a full track for a value that is really out of range.\n * Non-finite results are floored for the same reason. These results reach\n * assistive tech, because the same fraction drives the percentage substituted\n * into `aria-valuetext`.\n */\nexport function rangeFraction(value: number, min: number, max: number): number {\n const span = max - min;\n if (!(span > 0)) return 0;\n\n const clamped = Math.min(max, Math.max(min, value));\n let fraction: number;\n if (Number.isFinite(span)) {\n fraction = (clamped - min) / span;\n } else {\n // Scaling preserves the ratio when two finite endpoints straddle zero so\n // widely that their subtraction overflows to Infinity.\n const scale = Math.max(Math.abs(min), Math.abs(max));\n fraction = (clamped / scale - min / scale) / (max / scale - min / scale);\n }\n\n if (!Number.isFinite(fraction)) return 0;\n return Math.min(1, Math.max(0, fraction));\n}\n","/** Configuration for a value constrained to a stepped numeric range. */\nexport interface SteppedRange {\n /** Inclusive lower endpoint. May be infinite for an unbounded control. */\n readonly min: number;\n /** Inclusive upper endpoint. May be infinite for an unbounded control. */\n readonly max: number;\n /** Requested step. Non-finite and non-positive values fall back to `1`. */\n readonly step: number;\n /** Step-grid origin. Defaults to a finite `min`, otherwise `0`. */\n readonly base?: number;\n}\n\n/** The positive finite step used at runtime and by keyboard stepping. */\nexport function effectiveStep(step: number): number {\n return Number.isFinite(step) && step > 0 ? step : 1;\n}\n\n/**\n * Clamps a raw value and selects its nearest allowed value.\n *\n * The allowed set is the step grid plus each finite range endpoint. Keeping the\n * endpoints in that set makes an authored `aria-valuemin`/`aria-valuemax`\n * truthful even when the upper endpoint is not aligned to the grid. A value\n * exactly between two allowed values resolves upward.\n */\nexport function snapSteppedValue(raw: number, range: SteppedRange): number {\n if (!(range.min <= range.max)) return finiteFallback(range.min, range.max);\n\n const input = Number.isNaN(raw) ? finiteFallback(range.min, range.max) : raw;\n const clamped = Math.min(range.max, Math.max(range.min, input));\n const step = effectiveStep(range.step);\n const base = stepBase(range);\n const candidates: number[] = [];\n\n if (Number.isFinite(range.min)) candidates.push(range.min);\n if (Number.isFinite(range.max)) candidates.push(range.max);\n\n const gridPosition = (clamped - base) / step;\n if (Number.isFinite(gridPosition)) {\n addGridCandidate(candidates, Math.floor(gridPosition), range, base, step);\n addGridCandidate(candidates, Math.ceil(gridPosition), range, base, step);\n }\n\n if (candidates.length === 0) return clamped;\n let nearest = candidates[0] as number;\n let nearestDistance = Math.abs(clamped - nearest);\n for (const candidate of candidates.slice(1)) {\n const distance = Math.abs(clamped - candidate);\n if (\n distance < nearestDistance ||\n (nearlyEqual(distance, nearestDistance) && candidate > nearest)\n ) {\n nearest = candidate;\n nearestDistance = distance;\n }\n }\n return nearest;\n}\n\n/**\n * Moves through the ordered allowed-value set by an integer number of steps.\n *\n * This differs from adding `step * count` and snapping afterward at an\n * off-grid endpoint: with `max=94, step=10`, one decrement from `94` must land\n * on `90`, and one increment from `90` must reach the advertised maximum `94`.\n */\nexport function stepSteppedValue(current: number, count: number, range: SteppedRange): number {\n const value = snapSteppedValue(current, range);\n const distance = Math.abs(Math.trunc(count));\n if (!Number.isFinite(distance) || distance === 0) return value;\n const direction = Math.sign(count);\n const adjacent = adjacentSteppedValue(value, direction, range);\n\n // The first move accounts for a possibly off-grid endpoint. From there a\n // direct grid jump is equivalent to repeated adjacency, and avoids work that\n // grows with Page-step magnitudes or programmatic counts.\n const raw = adjacent + direction * (distance - 1) * effectiveStep(range.step);\n return snapSteppedValue(raw, range);\n}\n\n/** Returns the next member of the ordered endpoint-plus-grid set. */\nfunction adjacentSteppedValue(current: number, direction: number, range: SteppedRange): number {\n const step = effectiveStep(range.step);\n const base = stepBase(range);\n const candidates: number[] = [];\n\n if (direction > 0) {\n const position = (current - base) / step;\n if (Number.isFinite(position)) {\n let gridIndex = Math.floor(position) + 1;\n let candidate = cleanGridValue(base + gridIndex * step, base, step);\n if (candidate < current || nearlyEqual(candidate, current)) {\n gridIndex += 1;\n candidate = cleanGridValue(base + gridIndex * step, base, step);\n }\n if (\n candidate > current &&\n !nearlyEqual(candidate, current) &&\n within(candidate, range.min, range.max)\n ) {\n candidates.push(clamp(candidate, range));\n }\n }\n return clamp(Math.min(...candidates), range);\n }\n\n const position = (current - base) / step;\n if (Number.isFinite(position)) {\n let gridIndex = Math.ceil(position) - 1;\n let candidate = cleanGridValue(base + gridIndex * step, base, step);\n if (candidate > current || nearlyEqual(candidate, current)) {\n gridIndex -= 1;\n candidate = cleanGridValue(base + gridIndex * step, base, step);\n }\n if (\n candidate < current &&\n !nearlyEqual(candidate, current) &&\n within(candidate, range.min, range.max)\n ) {\n candidates.push(clamp(candidate, range));\n }\n }\n return clamp(Math.max(...candidates), range);\n}\n\n/** Adds one grid value when it falls inside the inclusive range. */\nfunction addGridCandidate(\n candidates: number[],\n index: number,\n range: SteppedRange,\n base: number,\n step: number,\n): void {\n const candidate = cleanGridValue(base + index * step, base, step);\n if (within(candidate, range.min, range.max)) candidates.push(clamp(candidate, range));\n}\n\n/** A finite authored base wins; otherwise the finite lower bound or zero does. */\nfunction stepBase(range: SteppedRange): number {\n if (range.base !== undefined && Number.isFinite(range.base)) return range.base;\n return Number.isFinite(range.min) ? range.min : 0;\n}\n\n/** Removes binary noise without discarding significant integer-place digits. */\nfunction cleanGridValue(value: number, base: number, step: number): number {\n const precision = Math.max(decimalPlaces(base), decimalPlaces(step));\n // `toFixed` accepts at most 100 fractional digits. Below that limit it rounds\n // according to the authored grid precision rather than an arbitrary total\n // significant-digit count, which keeps unit steps distinct at large bases.\n return precision <= 100 ? Number(value.toFixed(precision)) : value;\n}\n\n/** Number of fractional decimal places represented by a finite JS number. */\nfunction decimalPlaces(value: number): number {\n const [coefficient = \"\", exponentText] = Math.abs(value).toString().toLowerCase().split(\"e\");\n const fractionLength = coefficient.split(\".\")[1]?.length ?? 0;\n const exponent = exponentText === undefined ? 0 : Number(exponentText);\n return Math.max(0, fractionLength - exponent);\n}\n\n/** Inclusive range check tolerant only of arithmetic noise at an endpoint. */\nfunction within(value: number, min: number, max: number): boolean {\n return (value > min || nearlyEqual(value, min)) && (value < max || nearlyEqual(value, max));\n}\n\n/** Clamps a candidate whose only overshoot may be floating-point noise. */\nfunction clamp(value: number, range: SteppedRange): number {\n return Math.min(range.max, Math.max(range.min, value));\n}\n\n/** Relative comparison used only to collapse arithmetic noise, not real steps. */\nfunction nearlyEqual(left: number, right: number): boolean {\n const scale = Math.max(Math.abs(left), Math.abs(right));\n return (\n Number.isFinite(left) &&\n Number.isFinite(right) &&\n Math.abs(left - right) <= Number.EPSILON * scale\n );\n}\n\n/** Stable fallback for malformed ranges or `NaN` input. */\nfunction finiteFallback(min: number, max: number): number {\n if (Number.isFinite(min)) return min;\n if (Number.isFinite(max)) return max;\n return 0;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { OwnedPointerSession } from \"../utils/owned_pointer_session\";\nimport { rangeFraction } from \"../utils/range\";\nimport { type SteppedRange, snapSteppedValue, stepSteppedValue } from \"../utils/stepped_value\";\n\n/** CSS custom properties exposing each thumb's position (0..1) to the consumer. */\nconst START_PROPERTY = \"--stimeo--range-slider-start\";\nconst END_PROPERTY = \"--stimeo--range-slider-end\";\nconst DEFAULT_MIN = 0;\nconst DEFAULT_MAX = 100;\n\n/**\n * Headless, accessible **two-thumb range slider** behavior (APG Slider —\n * Multi-Thumb). A derivative of {@link SliderController}: it manages two thumbs\n * (`start` ≤ `end`) that constrain each other.\n *\n * Markup contract (identifier: `stimeo--range-slider`):\n * <div data-controller=\"stimeo--range-slider\"\n * data-stimeo--range-slider-min-value=\"0\"\n * data-stimeo--range-slider-max-value=\"100\"\n * data-stimeo--range-slider-step-value=\"1\"\n * data-stimeo--range-slider-start-value=\"20\"\n * data-stimeo--range-slider-end-value=\"80\">\n * <div data-stimeo--range-slider-target=\"track\"\n * data-action=\"pointerdown->stimeo--range-slider#onPointerDown\">\n * <div role=\"slider\" tabindex=\"0\" aria-label=\"Minimum\"\n * data-stimeo--range-slider-target=\"startThumb\"\n * data-action=\"keydown->stimeo--range-slider#onKeydown\"></div>\n * <div role=\"slider\" tabindex=\"0\" aria-label=\"Maximum\"\n * data-stimeo--range-slider-target=\"endThumb\"\n * data-action=\"keydown->stimeo--range-slider#onKeydown\"></div>\n * </div>\n * </div>\n *\n * @remarks\n * Behavior only — the consumer owns all layout (positioning the thumbs and the\n * selected range from the fractions). Only the horizontal orientation is handled.\n * Each thumb's movable range is bounded by the *other* thumb's current value,\n * reflected on its `aria-valuemin`/`aria-valuemax` so assistive tech announces the\n * live constraint.\n *\n * Behavior provided (per focused thumb):\n * - `ArrowRight`/`ArrowUp` increase and `ArrowLeft`/`ArrowDown` decrease by one\n * step; `Home`/`End` jump to that thumb's movable min/max; `PageUp`/`PageDown`\n * move by ten steps. A thumb never crosses the other.\n * - Pointer press/drag on the track moves the nearest thumb.\n * When the thumbs overlap, a press above their shared value selects `end`\n * and a press below selects `start`, so the pair remains expandable.\n * - `min`, `max`, `start`, and `end` must be finite. Invalid runtime endpoints\n * fall back to `0`/`100`; invalid thumb Values fall back to the corresponding\n * effective endpoint. A `max` below `min` collapses the range to `min`.\n * - `step` must be finite and positive; invalid runtime input falls back to `1`.\n * Finite movable endpoints remain allowed off the shared step grid.\n *\n * The fractions are value ratios, not positions, so only the consumer knows\n * whether their track mirrors under RTL. Set `logicalTrack` to declare that it\n * does: the pointer mapping and the horizontal arrow pair then follow the\n * writing direction. Left unset, nothing here reads `direction`.\n */\nexport class RangeSliderController extends Controller<HTMLElement> {\n static override targets = [\"track\", \"startThumb\", \"endThumb\"];\n static override values = {\n min: { type: Number, default: DEFAULT_MIN },\n max: { type: Number, default: DEFAULT_MAX },\n step: { type: Number, default: 1 },\n start: { type: Number, default: 0 },\n end: { type: Number, default: 100 },\n logicalTrack: { type: Boolean, default: false },\n };\n static actions = [\"onKeydown\", \"onPointerDown\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly trackTarget: HTMLElement;\n declare readonly startThumbTarget: HTMLElement;\n declare readonly endThumbTarget: HTMLElement;\n declare readonly hasTrackTarget: boolean;\n declare readonly hasStartThumbTarget: boolean;\n declare readonly hasEndThumbTarget: boolean;\n declare minValue: number;\n declare maxValue: number;\n declare stepValue: number;\n declare startValue: number;\n declare endValue: number;\n declare logicalTrackValue: boolean;\n\n /** One initiating pointer owns each live drag and its stable target snapshot. */\n #drag: RangeSliderDrag | null = null;\n\n /** Whether the consumer declared a mirroring track and the direction mirrors it. */\n get #mirrored(): boolean {\n return this.logicalTrackValue && isRtl(this.element);\n }\n\n /**\n * Collapses a morph that swaps render inputs into one repaint, and refuses the\n * pass Stimulus delivers before `connect()`.\n */\n readonly #repaint = new MicrotaskCoalescer(() => this.#render());\n\n override connect(): void {\n this.#repaint.activate();\n const range = this.#effectiveRange;\n const pair = this.#currentPair(range);\n this.#commit(pair.start, pair.end, null, false);\n }\n\n /** Cancels any active pointer drag so document listeners never leak. */\n override disconnect(): void {\n this.#repaint.cancel();\n this.#endDrag();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `min` at runtime. */\n minValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `max` at runtime. */\n maxValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `step` at runtime. */\n stepValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `start` at runtime. */\n startValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `end` at runtime. */\n endValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Hydrates a replacement start thumb and restores live-drag focus ownership. */\n startThumbTargetConnected(thumb: HTMLElement): void {\n const range = this.#effectiveRange;\n const pair = this.#currentPair(range);\n this.#renderStartThumb(thumb, pair, range);\n this.#renderFractions(pair, range);\n if (this.#drag?.kind === \"start\" && this.#drag.thumb === null) {\n this.#drag.thumb = thumb;\n thumb.focus();\n }\n this.#repaint.schedule();\n }\n\n /** Drops a stale start-thumb reference without orphaning the track gesture. */\n startThumbTargetDisconnected(thumb: HTMLElement): void {\n if (this.#drag?.kind === \"start\" && this.#drag.thumb === thumb) this.#drag.thumb = null;\n }\n\n /** Hydrates a replacement end thumb and restores live-drag focus ownership. */\n endThumbTargetConnected(thumb: HTMLElement): void {\n const range = this.#effectiveRange;\n const pair = this.#currentPair(range);\n this.#renderEndThumb(thumb, pair, range);\n this.#renderFractions(pair, range);\n if (this.#drag?.kind === \"end\" && this.#drag.thumb === null) {\n this.#drag.thumb = thumb;\n thumb.focus();\n }\n this.#repaint.schedule();\n }\n\n /** Drops a stale end-thumb reference without orphaning the track gesture. */\n endThumbTargetDisconnected(thumb: HTMLElement): void {\n if (this.#drag?.kind === \"end\" && this.#drag.thumb === thumb) this.#drag.thumb = null;\n }\n\n /** Ends a gesture whose geometry target disappeared or ceased being a target. */\n trackTargetDisconnected(track: HTMLElement): void {\n if (this.#drag?.track === track) this.#endDrag();\n }\n\n /** Keyboard stepping for whichever thumb is focused (the action's element). */\n onKeydown(event: KeyboardEvent): void {\n if (isReservedArrowChord(event)) return;\n const thumb = event.currentTarget as HTMLElement;\n const isStart = this.hasStartThumbTarget && thumb === this.startThumbTarget;\n const isEnd = this.hasEndThumbTarget && thumb === this.endThumbTarget;\n const effectiveRange = this.#effectiveRange;\n const pair = this.#currentPair(effectiveRange);\n let kind: RangeThumb;\n let current: number;\n let range: { min: number; max: number; step: number; base: number };\n if (isStart) {\n kind = \"start\";\n current = pair.start;\n range = {\n min: effectiveRange.min,\n max: pair.end,\n step: effectiveRange.step,\n base: effectiveRange.min,\n };\n } else {\n if (!isEnd) return;\n kind = \"end\";\n current = pair.end;\n range = {\n min: pair.start,\n max: effectiveRange.max,\n step: effectiveRange.step,\n base: effectiveRange.min,\n };\n }\n\n let next: number | null = null;\n // On a mirrored track the greater value sits at the visual left, so the\n // horizontal pair trades places; `Home`/`End` name bounds, not a direction.\n switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = stepSteppedValue(current, 1, range);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = stepSteppedValue(current, -1, range);\n break;\n case \"PageUp\":\n next = stepSteppedValue(current, 10, range);\n break;\n case \"PageDown\":\n next = stepSteppedValue(current, -10, range);\n break;\n case \"Home\":\n next = range.min;\n break;\n case \"End\":\n next = range.max;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#moveThumb(kind, next);\n }\n\n /** Begins a pointer drag on the track, moving the thumb nearest the press. */\n onPointerDown(event: PointerEvent): void {\n if (\n event.button !== 0 ||\n this.#drag ||\n !this.hasTrackTarget ||\n !this.hasStartThumbTarget ||\n !this.hasEndThumbTarget\n ) {\n return;\n }\n const track = this.trackTarget;\n // Resolve the direction once for the whole gesture: reading it per move\n // would query computed style on every frame, and a drag that flipped\n // mid-gesture would be incoherent anyway.\n const mirrored = this.#mirrored;\n const value = this.#valueFromClientX(event.clientX, mirrored, track);\n if (value === null) return;\n event.preventDefault();\n\n // Pick the nearer thumb. An ordinary midpoint tie stays deterministic on\n // start; an overlapped pair uses the press direction so it can expand.\n // Only read a target getter behind its has*Target guard — Stimulus throws\n // when an absent target is accessed.\n const pair = this.#currentPair(this.#effectiveRange);\n const kind = this.#nearestThumb(value, pair);\n let thumb: HTMLElement;\n if (kind === \"start\") thumb = this.startThumbTarget;\n else thumb = this.endThumbTarget;\n this.#moveThumb(kind, value);\n thumb.focus();\n\n const drag: RangeSliderDrag = { pointer: null, track, thumb, kind };\n drag.pointer = new OwnedPointerSession(event, track, {\n move: (move) => {\n if (!track.isConnected) {\n this.#endDrag();\n return;\n }\n const moved = this.#valueFromClientX(move.clientX, mirrored, track);\n if (moved !== null) this.#moveThumb(kind, moved);\n },\n end: () => {\n if (this.#drag === drag) this.#drag = null;\n },\n });\n this.#drag = drag;\n }\n\n /** Maps a pointer X coordinate to a raw value using the track geometry. */\n #valueFromClientX(clientX: number, mirrored: boolean, track: HTMLElement): number | null {\n const rect = track.getBoundingClientRect();\n if (rect.width === 0) return null;\n const offset = (clientX - rect.left) / rect.width;\n const fraction = mirrored ? 1 - offset : offset;\n const range = this.#effectiveRange;\n return range.min + fraction * (range.max - range.min);\n }\n\n /**\n * Chooses the closest thumb while keeping an overlapped pair expandable.\n * Ordinary midpoint ties stay deterministic on `start`; at an overlap, a\n * press above the shared value selects `end` and a press below selects\n * `start`.\n */\n #nearestThumb(value: number, pair: RangePair): RangeThumb {\n const startDistance = Math.abs(value - pair.start);\n const endDistance = Math.abs(value - pair.end);\n if (endDistance < startDistance) return \"end\";\n if (pair.start === pair.end && value > pair.start) return \"end\";\n return \"start\";\n }\n\n /** Moves one thumb to a new raw value, keeping the pair ordered. */\n #moveThumb(kind: RangeThumb, raw: number): void {\n const pair = this.#currentPair(this.#effectiveRange);\n if (kind === \"start\") {\n this.#commit(raw, pair.end, \"start\", true);\n } else {\n this.#commit(pair.start, raw, \"end\", true);\n }\n }\n\n /**\n * Clamps and snaps `start`/`end`, enforces `start ≤ end`, stores the pair, and\n * reflects it onto the thumbs' ARIA attributes and the range custom\n * properties. Dispatches `stimeo--range-slider:change` only when a\n * user-driven update changes the normalized pair, with\n * `{ start: number, end: number }` in `detail`.\n */\n #commit(start: number, end: number, moving: RangeThumb | null, notify: boolean): void {\n const range = this.#effectiveRange;\n const previous = this.#currentPair(range);\n let nextStart = snapSteppedValue(start, range);\n let nextEnd = snapSteppedValue(end, range);\n // Keep them ordered: a thumb pushed past its partner stops at the partner.\n if (nextStart > nextEnd) {\n if (moving === \"start\") nextStart = nextEnd;\n else if (moving === \"end\") nextEnd = nextStart;\n else [nextStart, nextEnd] = [nextEnd, nextStart];\n }\n\n if (!Object.is(this.startValue, nextStart)) this.startValue = nextStart;\n if (!Object.is(this.endValue, nextEnd)) this.endValue = nextEnd;\n const pair = { start: nextStart, end: nextEnd };\n this.#renderPair(pair, range);\n\n if (notify && (nextStart !== previous.start || nextEnd !== previous.end)) {\n this.dispatch(\"change\", { detail: { start: nextStart, end: nextEnd } });\n }\n }\n\n /**\n * Reflects morph-supplied Values without writing them back or dispatching.\n *\n * @stimeoRenderRoot\n */\n #render(): void {\n const range = this.#effectiveRange;\n this.#renderPair(this.#currentPair(range), range);\n }\n\n /** Reflects one normalized pair onto both thumbs and CSS properties. */\n #renderPair(pair: RangePair, range: SteppedRange): void {\n if (this.hasStartThumbTarget) {\n this.#renderStartThumb(this.startThumbTarget, pair, range);\n }\n if (this.hasEndThumbTarget) {\n this.#renderEndThumb(this.endThumbTarget, pair, range);\n }\n this.#renderFractions(pair, range);\n }\n\n /** Writes only changed ARIA for the lower thumb. */\n #renderStartThumb(thumb: HTMLElement, pair: RangePair, range: SteppedRange): void {\n this.#setAria(thumb, \"aria-valuemin\", range.min);\n this.#setAria(thumb, \"aria-valuemax\", pair.end);\n this.#setAria(thumb, \"aria-valuenow\", pair.start);\n }\n\n /** Writes only changed ARIA for the upper thumb. */\n #renderEndThumb(thumb: HTMLElement, pair: RangePair, range: SteppedRange): void {\n this.#setAria(thumb, \"aria-valuemin\", pair.start);\n this.#setAria(thumb, \"aria-valuemax\", range.max);\n this.#setAria(thumb, \"aria-valuenow\", pair.end);\n }\n\n /** Writes one numeric ARIA attribute only when its serialized value changed. */\n #setAria(thumb: HTMLElement, name: string, value: number): void {\n const next = String(value);\n if (thumb.getAttribute(name) !== next) thumb.setAttribute(name, next);\n }\n\n /** Writes the two behavior-only fraction hooks only when they changed. */\n #renderFractions(pair: RangePair, range: SteppedRange): void {\n const start = String(rangeFraction(pair.start, range.min, range.max));\n const end = String(rangeFraction(pair.end, range.min, range.max));\n if (this.element.style.getPropertyValue(START_PROPERTY) !== start) {\n this.element.style.setProperty(START_PROPERTY, start);\n }\n if (this.element.style.getPropertyValue(END_PROPERTY) !== end) {\n this.element.style.setProperty(END_PROPERTY, end);\n }\n }\n\n /** Current normalized and ordered pair derived from live declarative Values. */\n #currentPair(range: SteppedRange): RangePair {\n const rawStart = Number.isFinite(this.startValue) ? this.startValue : range.min;\n const rawEnd = Number.isFinite(this.endValue) ? this.endValue : range.max;\n const start = snapSteppedValue(rawStart, range);\n const end = snapSteppedValue(rawEnd, range);\n if (start <= end) return { start, end };\n return { start: end, end: start };\n }\n\n /**\n * Finite ordered range used by ARIA, keyboard, pointer, and CSS reflection.\n * Invalid endpoints fall back to the public defaults; an authored `max`\n * below `min` collapses to the finite minimum.\n */\n get #effectiveRange(): SteppedRange {\n const min = Number.isFinite(this.minValue) ? this.minValue : DEFAULT_MIN;\n const authoredMax = Number.isFinite(this.maxValue) ? this.maxValue : DEFAULT_MAX;\n return { min, max: Math.max(min, authoredMax), step: this.stepValue, base: min };\n }\n\n /** Ends the current pointer session without dispatching another change. */\n #endDrag(): void {\n const drag = this.#drag;\n this.#drag = null;\n drag?.pointer?.end();\n }\n}\n\n/** Which thumb owns a keyboard or pointer move. */\ntype RangeThumb = \"start\" | \"end\";\n\n/** One normalized pair reflected on the two public slider roles. */\ninterface RangePair {\n readonly start: number;\n readonly end: number;\n}\n\n/** Stable DOM and gesture state owned for the duration of one pointer drag. */\ninterface RangeSliderDrag {\n pointer: OwnedPointerSession | null;\n readonly track: HTMLElement;\n thumb: HTMLElement | null;\n readonly kind: RangeThumb;\n}\n"]}
@@ -175,6 +175,8 @@ var RatingController = class extends Controller {
175
175
  * Applies `value` (already clamped) everywhere, then dispatches `change`.
176
176
  * Use for user-driven changes; on connect call `#apply` directly so
177
177
  * initialization mirrors state without emitting an event.
178
+ *
179
+ * @stimeoRenderRoot
178
180
  */
179
181
  #render(value, { focus }) {
180
182
  this.#apply(value, { focus });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/roving_tabindex.ts","../../src/controllers/rating_controller.ts"],"names":[],"mappings":"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","file":"rating_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Collapses many target callbacks from one DOM mutation into a single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element, so\n * replacing a list of N options delivers N callbacks — but the useful unit of\n * work is \"reconcile against the DOM that resulted\", once, after the batch has\n * settled. Every controller that owns a reconcilable target set needs the same\n * shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers the *initial* target callbacks\n * ahead of `connect()`. Reconciling there would compute a fallback against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Low-level roving-tabindex primitive shared by composite-widget controllers.\n *\n * The APG roving-tabindex pattern keeps a composite widget a single Tab stop:\n * exactly one item is in the Tab sequence (`tabindex=\"0\"`) while the rest are\n * removed from it (`tabindex=\"-1\"`), and the arrow keys move both DOM focus and\n * that single tabbable position together. {@link RovingTabindex} owns *only* that\n * mechanical bookkeeping — \"which one item is tabbable, and move focus there\".\n *\n * It is intentionally **policy-free**. Orientation, wrapping vs. clamping,\n * selection-follows-focus, typeahead, and `Home`/`End` semantics differ per APG\n * pattern (Radio Group, Toolbar, Rating, …); folding them into one helper would\n * flatten those widgets to a lowest common denominator and lose each pattern's\n * correctness. Those decisions therefore stay in each controller, which calls\n * {@link RovingTabindex.setActive} with an index it computed itself (optionally\n * via the pure {@link rovingMove} helper).\n *\n * @remarks\n * Items are read lazily through a getter so a controller can add or remove\n * targets (Stimulus re-scans the DOM) without re-wiring this helper.\n */\nexport class RovingTabindex {\n /** Returns the current ordered item elements; called on every operation. */\n readonly #getItems: () => HTMLElement[];\n\n /**\n * @param getItems - Returns the current ordered item elements. Called on every\n * operation so the live target list is always used.\n */\n constructor(getItems: () => HTMLElement[]) {\n this.#getItems = getItems;\n }\n\n /** Index of the currently tabbable item (`tabindex=\"0\"`), or `-1` if none. */\n get activeIndex(): number {\n return this.#getItems().findIndex((item) => item.tabIndex === 0);\n }\n\n /**\n * Makes exactly the item at `index` tabbable (`tabindex=\"0\"`) and removes every\n * other item from the Tab sequence (`tabindex=\"-1\"`). An out-of-range `index`\n * (e.g. `-1`) leaves all items at `-1`, which a controller can use to express\n * \"nothing is currently tabbable\".\n *\n * @param index - Position of the item to make tabbable.\n * @param options - Pass `{ focus: true }` to also move DOM focus to that item.\n */\n setActive(index: number, { focus = false }: { focus?: boolean } = {}): void {\n const items = this.#getItems();\n items.forEach((item, i) => {\n item.tabIndex = i === index ? 0 : -1;\n });\n if (focus) items[index]?.focus();\n }\n}\n\n/** Edge behavior for {@link rovingMove}: cycle past the ends, or stop at them. */\nexport type RovingWrap = \"wrap\" | \"clamp\";\n\n/**\n * Pure helper that resolves the target index for a one-step directional move.\n *\n * Keyboard/orientation mapping stays in the caller: it decides that a key means\n * `delta` `+1` (next) or `-1` (previous) and whether the widget should `\"wrap\"`\n * (Radio Group, Toolbar) or `\"clamp\"` at the ends.\n *\n * @param current - The index focus is moving from.\n * @param length - Number of items in the set.\n * @param delta - `+1` to move to the next item, `-1` for the previous.\n * @param wrap - `\"wrap\"` cycles around the ends; `\"clamp\"` stops at them.\n * @returns The resolved index, or `-1` when there are no items.\n */\nexport function rovingMove(\n current: number,\n length: number,\n delta: number,\n wrap: RovingWrap,\n): number {\n if (length === 0) return -1;\n const next = current + delta;\n if (wrap === \"wrap\") return (next + length) % length;\n return Math.min(length - 1, Math.max(0, next));\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { RovingTabindex } from \"../utils/roving_tabindex\";\n\n/**\n * Headless, accessible rating (star) behavior.\n *\n * Markup contract (identifier: `stimeo--rating`):\n * <div data-controller=\"stimeo--rating\" role=\"radiogroup\" aria-label=\"Rating\"\n * data-stimeo--rating-value-value=\"3\" data-stimeo--rating-max-value=\"5\">\n * <span role=\"radio\" aria-checked=\"false\" aria-label=\"1 star\" tabindex=\"-1\"\n * data-rating-value=\"1\" data-stimeo--rating-target=\"symbol\"\n * data-action=\"click->stimeo--rating#select\n * mouseenter->stimeo--rating#preview\n * mouseleave->stimeo--rating#endPreview\n * focus->stimeo--rating#preview\n * blur->stimeo--rating#endPreview\n * keydown->stimeo--rating#onKeydown\">★</span>\n * <!-- symbols 2..max; the selected one (or the first when unrated) tabindex=0 -->\n * <input type=\"hidden\" data-stimeo--rating-target=\"field\" />\n * </div>\n *\n * Implements the WAI-ARIA APG **Radio Group** pattern (an ordinal scale): exactly\n * one symbol is `aria-checked`. Unlike a generic radio group it deliberately does\n * **not** wrap — being ordinal, arrows clamp at the bounds.\n *\n * @remarks\n * Behavior only — symbols are styled by the consumer off `[aria-checked]` and the\n * `data-rating-hover` fill hook. In `readonly` mode the group becomes\n * `role=\"img\"`: because Stimeo never emits human-readable prose, the accessible\n * name (e.g. \"Rated 3 of 5\") is the consumer's `aria-label`/`aria-labelledby`.\n *\n * Behavior provided:\n * - Click selects a symbol; clicking the selected symbol clears to 0 when\n * `clearable`.\n * - `ArrowRight`/`ArrowUp` raise and `ArrowLeft`/`ArrowDown` lower the value\n * (clamped, down to 0 when `clearable`); `Home`/`End` jump to min/max;\n * `Space`/`Enter` select the focused symbol.\n * - Hover/focus previews a fill range via `data-rating-hover`; leaving restores\n * the selected range. `stimeo--rating:change` is dispatched on every change.\n */\nexport class RatingController extends Controller<HTMLElement> {\n static override targets = [\"symbol\", \"field\"];\n static override values = {\n value: { type: Number, default: 0 },\n max: { type: Number, default: 5 },\n clearable: { type: Boolean, default: true },\n readonly: { type: Boolean, default: false },\n };\n static actions = [\"endPreview\", \"onKeydown\", \"preview\", \"select\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly symbolTargets: HTMLElement[];\n declare readonly fieldTarget: HTMLInputElement;\n declare readonly hasFieldTarget: boolean;\n declare valueValue: number;\n declare maxValue: number;\n declare clearableValue: boolean;\n declare readonlyValue: boolean;\n\n readonly #roving = new RovingTabindex(() => this.symbolTargets);\n\n /** Reflects the initial value, or switches to the non-interactive readonly view. */\n /**\n * Collapses a morph that swaps render inputs into one repaint, and refuses the\n * pass Stimulus delivers before `connect()`.\n */\n readonly #repaint = new MicrotaskCoalescer(() => {\n if (this.readonlyValue) {\n this.#applyReadonly();\n return;\n }\n this.#apply(this.#clamp(this.valueValue), { focus: false });\n });\n\n override connect(): void {\n this.#repaint.activate();\n if (this.readonlyValue) {\n this.#applyReadonly();\n return;\n }\n this.#apply(this.#clamp(this.valueValue), { focus: false });\n }\n\n /** Closes the window in which a queued repaint may still run. */\n override disconnect(): void {\n this.#repaint.cancel();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `value` at runtime. */\n valueValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Selects (or clears) the clicked symbol. Bound via `data-action` (click). */\n select(event: Event): void {\n if (this.readonlyValue) return;\n // Clamp the clicked value so a malformed data-rating-value can never push the\n // roving Tab stop out of range.\n const value = this.#clamp(this.#symbolValue(event.currentTarget as HTMLElement));\n if (this.clearableValue && value === this.valueValue) {\n // Clicking the selected symbol clears the rating; focus returns to the first.\n this.#render(0, { focus: true });\n } else {\n this.#render(value, { focus: false });\n }\n }\n\n /** Previews a fill range on hover/focus. Bound via `data-action` (mouseenter/focus). */\n preview(event: Event): void {\n if (this.readonlyValue) return;\n this.#setFillRange(this.#clamp(this.#symbolValue(event.currentTarget as HTMLElement)));\n }\n\n /** Restores the fill range to the selected value. Bound via `data-action` (mouseleave/blur). */\n endPreview(): void {\n if (this.readonlyValue) return;\n this.#setFillRange(this.valueValue);\n }\n\n /** Arrow/Home/End/Space keyboard control, clamped (no wrap). */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key must not ALSO change the rating —\n // composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n if (this.readonlyValue) return;\n let next: number | null = null;\n // Logical, not physical, for the horizontal pair only: the stars are an\n // ordered row, so which one is \"next\" follows the writing direction.\n //\n // The shared `logicalArrowStep` is deliberately not used here. It encodes the\n // list-order convention (`ArrowDown` = next), and this widget pairs the arrows\n // by *value* instead — `ArrowUp` means \"more\" — so borrowing it would silently\n // invert the vertical axis.\n const rtl = isRtl(this.element);\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = this.valueValue + (event.key === \"ArrowRight\" && rtl ? -1 : 1);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = this.valueValue - (event.key === \"ArrowLeft\" && rtl ? -1 : 1);\n break;\n case \"Home\":\n next = this.#minValue;\n break;\n case \"End\":\n next = this.maxValue;\n break;\n case \" \":\n case \"Enter\":\n next = this.#symbolValue(event.currentTarget as HTMLElement);\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#render(this.#clamp(next), { focus: true });\n }\n\n /**\n * Applies `value` (already clamped) everywhere, then dispatches `change`.\n * Use for user-driven changes; on connect call `#apply` directly so\n * initialization mirrors state without emitting an event.\n */\n #render(value: number, { focus }: { focus: boolean }): void {\n this.#apply(value, { focus });\n this.dispatch(\"change\", { detail: { value } });\n }\n\n /**\n * Stores `value`, syncs `aria-checked` and the roving Tab stop, the hidden\n * field, and the fill range — without dispatching `change`. Idempotent and\n * safe on connect (and across Turbo morphing).\n */\n #apply(value: number, { focus }: { focus: boolean }): void {\n this.valueValue = value;\n this.symbolTargets.forEach((symbol) => {\n symbol.setAttribute(\n \"aria-checked\",\n value > 0 && this.#symbolValue(symbol) === value ? \"true\" : \"false\",\n );\n });\n // The selected symbol is the Tab stop; when unrated, the first symbol is.\n this.#roving.setActive(value > 0 ? value - 1 : 0, { focus });\n if (this.hasFieldTarget) this.fieldTarget.value = String(value);\n this.#setFillRange(value);\n }\n\n /** Marks symbols up to `range` with `data-rating-hover` (the consumer's fill hook). */\n #setFillRange(range: number): void {\n for (const symbol of this.symbolTargets) {\n if (this.#symbolValue(symbol) <= range && range > 0) {\n symbol.setAttribute(\"data-rating-hover\", \"\");\n } else {\n symbol.removeAttribute(\"data-rating-hover\");\n }\n }\n }\n\n /** Turns the group into a non-interactive `role=\"img\"` snapshot of the value. */\n #applyReadonly(): void {\n const value = this.#clamp(this.valueValue);\n this.valueValue = value;\n this.element.setAttribute(\"role\", \"img\");\n for (const symbol of this.symbolTargets) {\n // Drop the interactive radio role so the img has no nested interactive\n // descendants; aria-hidden removes them from the tree entirely.\n symbol.removeAttribute(\"role\");\n symbol.setAttribute(\"aria-hidden\", \"true\");\n symbol.tabIndex = -1;\n }\n if (this.hasFieldTarget) this.fieldTarget.value = String(value);\n this.#setFillRange(value);\n }\n\n /**\n * Clamps `value` to `[min, max]` (min is 0 when clearable, else 1). The upper\n * bound is also capped at the number of symbols so the roving Tab stop\n * (`value - 1`) always maps to a real symbol — even if the consumer's `max`\n * value and rendered symbol count disagree, the tabbable item is never lost.\n */\n #clamp(value: number): number {\n const max = Math.min(this.maxValue, this.symbolTargets.length);\n return Math.min(max, Math.max(this.#minValue, value));\n }\n\n /** Lowest selectable value: 0 when clearable, otherwise 1. */\n get #minValue(): number {\n return this.clearableValue ? 0 : 1;\n }\n\n /** A symbol's ordinal value (`data-rating-value`, defaulting to its position). */\n #symbolValue(symbol: HTMLElement): number {\n const raw = Number(symbol.getAttribute(\"data-rating-value\"));\n return Number.isFinite(raw) && raw > 0 ? raw : this.symbolTargets.indexOf(symbol) + 1;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/roving_tabindex.ts","../../src/controllers/rating_controller.ts"],"names":[],"mappings":"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","file":"rating_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Low-level roving-tabindex primitive shared by composite-widget controllers.\n *\n * The APG roving-tabindex pattern keeps a composite widget a single Tab stop:\n * exactly one item is in the Tab sequence (`tabindex=\"0\"`) while the rest are\n * removed from it (`tabindex=\"-1\"`), and the arrow keys move both DOM focus and\n * that single tabbable position together. {@link RovingTabindex} owns *only* that\n * mechanical bookkeeping — \"which one item is tabbable, and move focus there\".\n *\n * It is intentionally **policy-free**. Orientation, wrapping vs. clamping,\n * selection-follows-focus, typeahead, and `Home`/`End` semantics differ per APG\n * pattern (Radio Group, Toolbar, Rating, …); folding them into one helper would\n * flatten those widgets to a lowest common denominator and lose each pattern's\n * correctness. Those decisions therefore stay in each controller, which calls\n * {@link RovingTabindex.setActive} with an index it computed itself (optionally\n * via the pure {@link rovingMove} helper).\n *\n * @remarks\n * Items are read lazily through a getter so a controller can add or remove\n * targets (Stimulus re-scans the DOM) without re-wiring this helper.\n */\nexport class RovingTabindex {\n /** Returns the current ordered item elements; called on every operation. */\n readonly #getItems: () => HTMLElement[];\n\n /**\n * @param getItems - Returns the current ordered item elements. Called on every\n * operation so the live target list is always used.\n */\n constructor(getItems: () => HTMLElement[]) {\n this.#getItems = getItems;\n }\n\n /** Index of the currently tabbable item (`tabindex=\"0\"`), or `-1` if none. */\n get activeIndex(): number {\n return this.#getItems().findIndex((item) => item.tabIndex === 0);\n }\n\n /**\n * Makes exactly the item at `index` tabbable (`tabindex=\"0\"`) and removes every\n * other item from the Tab sequence (`tabindex=\"-1\"`). An out-of-range `index`\n * (e.g. `-1`) leaves all items at `-1`, which a controller can use to express\n * \"nothing is currently tabbable\".\n *\n * @param index - Position of the item to make tabbable.\n * @param options - Pass `{ focus: true }` to also move DOM focus to that item.\n */\n setActive(index: number, { focus = false }: { focus?: boolean } = {}): void {\n const items = this.#getItems();\n items.forEach((item, i) => {\n item.tabIndex = i === index ? 0 : -1;\n });\n if (focus) items[index]?.focus();\n }\n}\n\n/** Edge behavior for {@link rovingMove}: cycle past the ends, or stop at them. */\nexport type RovingWrap = \"wrap\" | \"clamp\";\n\n/**\n * Pure helper that resolves the target index for a one-step directional move.\n *\n * Keyboard/orientation mapping stays in the caller: it decides that a key means\n * `delta` `+1` (next) or `-1` (previous) and whether the widget should `\"wrap\"`\n * (Radio Group, Toolbar) or `\"clamp\"` at the ends.\n *\n * @param current - The index focus is moving from.\n * @param length - Number of items in the set.\n * @param delta - `+1` to move to the next item, `-1` for the previous.\n * @param wrap - `\"wrap\"` cycles around the ends; `\"clamp\"` stops at them.\n * @returns The resolved index, or `-1` when there are no items.\n */\nexport function rovingMove(\n current: number,\n length: number,\n delta: number,\n wrap: RovingWrap,\n): number {\n if (length === 0) return -1;\n const next = current + delta;\n if (wrap === \"wrap\") return (next + length) % length;\n return Math.min(length - 1, Math.max(0, next));\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { RovingTabindex } from \"../utils/roving_tabindex\";\n\n/**\n * Headless, accessible rating (star) behavior.\n *\n * Markup contract (identifier: `stimeo--rating`):\n * <div data-controller=\"stimeo--rating\" role=\"radiogroup\" aria-label=\"Rating\"\n * data-stimeo--rating-value-value=\"3\" data-stimeo--rating-max-value=\"5\">\n * <span role=\"radio\" aria-checked=\"false\" aria-label=\"1 star\" tabindex=\"-1\"\n * data-rating-value=\"1\" data-stimeo--rating-target=\"symbol\"\n * data-action=\"click->stimeo--rating#select\n * mouseenter->stimeo--rating#preview\n * mouseleave->stimeo--rating#endPreview\n * focus->stimeo--rating#preview\n * blur->stimeo--rating#endPreview\n * keydown->stimeo--rating#onKeydown\">★</span>\n * <!-- symbols 2..max; the selected one (or the first when unrated) tabindex=0 -->\n * <input type=\"hidden\" data-stimeo--rating-target=\"field\" />\n * </div>\n *\n * Implements the WAI-ARIA APG **Radio Group** pattern (an ordinal scale): exactly\n * one symbol is `aria-checked`. Unlike a generic radio group it deliberately does\n * **not** wrap — being ordinal, arrows clamp at the bounds.\n *\n * @remarks\n * Behavior only — symbols are styled by the consumer off `[aria-checked]` and the\n * `data-rating-hover` fill hook. In `readonly` mode the group becomes\n * `role=\"img\"`: because Stimeo never emits human-readable prose, the accessible\n * name (e.g. \"Rated 3 of 5\") is the consumer's `aria-label`/`aria-labelledby`.\n *\n * Behavior provided:\n * - Click selects a symbol; clicking the selected symbol clears to 0 when\n * `clearable`.\n * - `ArrowRight`/`ArrowUp` raise and `ArrowLeft`/`ArrowDown` lower the value\n * (clamped, down to 0 when `clearable`); `Home`/`End` jump to min/max;\n * `Space`/`Enter` select the focused symbol.\n * - Hover/focus previews a fill range via `data-rating-hover`; leaving restores\n * the selected range. `stimeo--rating:change` is dispatched on every change.\n */\nexport class RatingController extends Controller<HTMLElement> {\n static override targets = [\"symbol\", \"field\"];\n static override values = {\n value: { type: Number, default: 0 },\n max: { type: Number, default: 5 },\n clearable: { type: Boolean, default: true },\n readonly: { type: Boolean, default: false },\n };\n static actions = [\"endPreview\", \"onKeydown\", \"preview\", \"select\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly symbolTargets: HTMLElement[];\n declare readonly fieldTarget: HTMLInputElement;\n declare readonly hasFieldTarget: boolean;\n declare valueValue: number;\n declare maxValue: number;\n declare clearableValue: boolean;\n declare readonlyValue: boolean;\n\n readonly #roving = new RovingTabindex(() => this.symbolTargets);\n\n /** Reflects the initial value, or switches to the non-interactive readonly view. */\n /**\n * Collapses a morph that swaps render inputs into one repaint, and refuses the\n * pass Stimulus delivers before `connect()`.\n */\n readonly #repaint = new MicrotaskCoalescer(() => {\n if (this.readonlyValue) {\n this.#applyReadonly();\n return;\n }\n this.#apply(this.#clamp(this.valueValue), { focus: false });\n });\n\n override connect(): void {\n this.#repaint.activate();\n if (this.readonlyValue) {\n this.#applyReadonly();\n return;\n }\n this.#apply(this.#clamp(this.valueValue), { focus: false });\n }\n\n /** Closes the window in which a queued repaint may still run. */\n override disconnect(): void {\n this.#repaint.cancel();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `value` at runtime. */\n valueValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Selects (or clears) the clicked symbol. Bound via `data-action` (click). */\n select(event: Event): void {\n if (this.readonlyValue) return;\n // Clamp the clicked value so a malformed data-rating-value can never push the\n // roving Tab stop out of range.\n const value = this.#clamp(this.#symbolValue(event.currentTarget as HTMLElement));\n if (this.clearableValue && value === this.valueValue) {\n // Clicking the selected symbol clears the rating; focus returns to the first.\n this.#render(0, { focus: true });\n } else {\n this.#render(value, { focus: false });\n }\n }\n\n /** Previews a fill range on hover/focus. Bound via `data-action` (mouseenter/focus). */\n preview(event: Event): void {\n if (this.readonlyValue) return;\n this.#setFillRange(this.#clamp(this.#symbolValue(event.currentTarget as HTMLElement)));\n }\n\n /** Restores the fill range to the selected value. Bound via `data-action` (mouseleave/blur). */\n endPreview(): void {\n if (this.readonlyValue) return;\n this.#setFillRange(this.valueValue);\n }\n\n /** Arrow/Home/End/Space keyboard control, clamped (no wrap). */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key must not ALSO change the rating —\n // composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n if (this.readonlyValue) return;\n let next: number | null = null;\n // Logical, not physical, for the horizontal pair only: the stars are an\n // ordered row, so which one is \"next\" follows the writing direction.\n //\n // The shared `logicalArrowStep` is deliberately not used here. It encodes the\n // list-order convention (`ArrowDown` = next), and this widget pairs the arrows\n // by *value* instead — `ArrowUp` means \"more\" — so borrowing it would silently\n // invert the vertical axis.\n const rtl = isRtl(this.element);\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = this.valueValue + (event.key === \"ArrowRight\" && rtl ? -1 : 1);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = this.valueValue - (event.key === \"ArrowLeft\" && rtl ? -1 : 1);\n break;\n case \"Home\":\n next = this.#minValue;\n break;\n case \"End\":\n next = this.maxValue;\n break;\n case \" \":\n case \"Enter\":\n next = this.#symbolValue(event.currentTarget as HTMLElement);\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#render(this.#clamp(next), { focus: true });\n }\n\n /**\n * Applies `value` (already clamped) everywhere, then dispatches `change`.\n * Use for user-driven changes; on connect call `#apply` directly so\n * initialization mirrors state without emitting an event.\n *\n * @stimeoRenderRoot\n */\n #render(value: number, { focus }: { focus: boolean }): void {\n this.#apply(value, { focus });\n this.dispatch(\"change\", { detail: { value } });\n }\n\n /**\n * Stores `value`, syncs `aria-checked` and the roving Tab stop, the hidden\n * field, and the fill range — without dispatching `change`. Idempotent and\n * safe on connect (and across Turbo morphing).\n */\n #apply(value: number, { focus }: { focus: boolean }): void {\n this.valueValue = value;\n this.symbolTargets.forEach((symbol) => {\n symbol.setAttribute(\n \"aria-checked\",\n value > 0 && this.#symbolValue(symbol) === value ? \"true\" : \"false\",\n );\n });\n // The selected symbol is the Tab stop; when unrated, the first symbol is.\n this.#roving.setActive(value > 0 ? value - 1 : 0, { focus });\n if (this.hasFieldTarget) this.fieldTarget.value = String(value);\n this.#setFillRange(value);\n }\n\n /** Marks symbols up to `range` with `data-rating-hover` (the consumer's fill hook). */\n #setFillRange(range: number): void {\n for (const symbol of this.symbolTargets) {\n if (this.#symbolValue(symbol) <= range && range > 0) {\n symbol.setAttribute(\"data-rating-hover\", \"\");\n } else {\n symbol.removeAttribute(\"data-rating-hover\");\n }\n }\n }\n\n /** Turns the group into a non-interactive `role=\"img\"` snapshot of the value. */\n #applyReadonly(): void {\n const value = this.#clamp(this.valueValue);\n this.valueValue = value;\n this.element.setAttribute(\"role\", \"img\");\n for (const symbol of this.symbolTargets) {\n // Drop the interactive radio role so the img has no nested interactive\n // descendants; aria-hidden removes them from the tree entirely.\n symbol.removeAttribute(\"role\");\n symbol.setAttribute(\"aria-hidden\", \"true\");\n symbol.tabIndex = -1;\n }\n if (this.hasFieldTarget) this.fieldTarget.value = String(value);\n this.#setFillRange(value);\n }\n\n /**\n * Clamps `value` to `[min, max]` (min is 0 when clearable, else 1). The upper\n * bound is also capped at the number of symbols so the roving Tab stop\n * (`value - 1`) always maps to a real symbol — even if the consumer's `max`\n * value and rendered symbol count disagree, the tabbable item is never lost.\n */\n #clamp(value: number): number {\n const max = Math.min(this.maxValue, this.symbolTargets.length);\n return Math.min(max, Math.max(this.#minValue, value));\n }\n\n /** Lowest selectable value: 0 when clearable, otherwise 1. */\n get #minValue(): number {\n return this.clearableValue ? 0 : 1;\n }\n\n /** A symbol's ordinal value (`data-rating-value`, defaulting to its position). */\n #symbolValue(symbol: HTMLElement): number {\n const raw = Number(symbol.getAttribute(\"data-rating-value\"));\n return Number.isFinite(raw) && raw > 0 ? raw : this.symbolTargets.indexOf(symbol) + 1;\n }\n}\n"]}
@@ -208,6 +208,8 @@ var RelativeTimeController = class extends Controller {
208
208
  * polling can stop: a *past* timestamp that fell back to the absolute text can
209
209
  * never leave it again, and a locale the runtime rejects has nothing to render
210
210
  * until that value is corrected.
211
+ *
212
+ * @stimeoRenderRoot
211
213
  */
212
214
  #applyAndComputeDelay() {
213
215
  const deltaMs = this.#targetMs - Date.now();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/before_cache_reset.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/safe_timeout.ts","../../src/controllers/relative_time_controller.ts"],"names":[],"mappings":";;;;;AA2CO,IAAM,gBAAA,GAAN,MAAM,iBAAA,CAAiB;AAAA;AAAA,EAE5B,OAAgB,YAAA,mBAAe,IAAI,GAAA,EAAsB;AAAA;AAAA,EAGzD,OAAgB,iBAAiB,MAAY;AAC3C,IAAA,KAAA,MAAW,UAAA,IAAc,iBAAA,CAAiB,YAAA,EAAc,UAAA,CAAW,OAAA,EAAQ;AAAA,EAC7E,CAAA;AAAA,EAES,OAAA;AAAA;AAAA,EAGT,YAAY,MAAA,EAAoB;AAC9B,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,EACjB;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,MAAM,KAAA,GAAQ,iBAAA,CAAiB,YAAA,CAAa,IAAA,KAAS,CAAA;AACrD,IAAA,iBAAA,CAAiB,YAAA,CAAa,IAAI,IAAI,CAAA;AACtC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,QAAA,CAAS,gBAAA,CAAiB,oBAAA,EAAsB,iBAAA,CAAiB,cAAc,CAAA;AAAA,IACjF;AAAA,EACF;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,iBAAA,CAAiB,YAAA,CAAa,OAAO,IAAI,CAAA;AACzC,IAAA,IAAI,iBAAA,CAAiB,YAAA,CAAa,IAAA,GAAO,CAAA,EAAG;AAC5C,IAAA,QAAA,CAAS,mBAAA,CAAoB,oBAAA,EAAsB,iBAAA,CAAiB,cAAc,CAAA;AAAA,EACpF;AACF,CAAA;;;ACvBO,IAAM,qBAAN,MAAyB;AAAA,EACrB,IAAA;AAAA,EACT,OAAA,GAAU,KAAA;AAAA,EACV,OAAA,GAAU,KAAA;AAAA,EACV,WAAA,GAAc,CAAA;AAAA;AAAA,EAGd,YAAY,GAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,EACjB;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,WAAA,IAAe,CAAA;AAAA,EACtB;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,EAAS;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,cAAA,CAAe,MAAM;AAEnB,MAAA,IAAI,UAAA,KAAe,KAAK,WAAA,IAAe,CAAC,KAAK,OAAA,IAAW,CAAC,KAAK,OAAA,EAAS;AACvE,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;;AC9DA,IAAe,gBAAf,MAA6B;AAAA;AAAA,EAER,GAAA,uBAAU,GAAA,EAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAczC,MAAM,EAAA,EAAkB;AACtB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,EAAE,CAAA,EAAG;AACvB,MAAA,IAAA,CAAK,OAAO,EAAE,CAAA;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAA,GAAiB;AACf,IAAA,KAAA,MAAW,EAAA,IAAM,KAAK,GAAA,EAAK;AACzB,MAAA,IAAA,CAAK,OAAO,EAAE,CAAA;AAAA,IAChB;AACA,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AAAA,EACjB;AAAA;AAAA,EAGA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,GAAA,CAAI,IAAA;AAAA,EAClB;AACF,CAAA;AAmBO,IAAM,WAAA,GAAN,cAA0B,aAAA,CAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO7C,GAAA,CAAI,UAAsB,KAAA,EAAuB;AAC/C,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,QAAA,CAAS,MAAM;AAC7B,MAAA,IAAA,CAAK,GAAA,CAAI,OAAO,EAAE,CAAA;AAClB,MAAA,QAAA,EAAS;AAAA,IACX,GAAG,KAAK,CAAA;AACR,IAAA,IAAA,CAAK,GAAA,CAAI,IAAI,EAAE,CAAA;AACf,IAAA,OAAO,EAAA;AAAA,EACT;AAAA,EAEU,QAAA,CAAS,UAAsB,KAAA,EAAuB;AAC9D,IAAA,OAAO,MAAA,CAAO,UAAA,CAAW,QAAA,EAAU,KAAK,CAAA;AAAA,EAC1C;AAAA,EAEU,OAAO,EAAA,EAAkB;AACjC,IAAA,MAAA,CAAO,aAAa,EAAE,CAAA;AAAA,EACxB;AACF,CAAA;;;AC1FA,IAAM,UAAA,GAAwB,EAAE,KAAA,EAAO,MAAA,CAAO,mBAAmB,IAAA,EAAM,MAAA,EAAQ,IAAI,QAAA,EAAe;AAGlG,IAAM,KAAA,GAA8B;AAAA,EAClC,EAAE,KAAA,EAAO,EAAA,EAAI,IAAA,EAAM,QAAA,EAAU,IAAI,GAAA,EAAK;AAAA,EACtC,EAAE,KAAA,EAAO,IAAA,EAAM,IAAA,EAAM,QAAA,EAAU,IAAI,GAAA,EAAO;AAAA,EAC1C,EAAE,KAAA,EAAO,KAAA,EAAQ,IAAA,EAAM,MAAA,EAAQ,IAAI,IAAA,EAAU;AAAA,EAC7C,EAAE,KAAA,EAAO,MAAA,EAAS,IAAA,EAAM,KAAA,EAAO,IAAI,KAAA,EAAW;AAAA,EAC9C,EAAE,KAAA,EAAO,OAAA,EAAW,IAAA,EAAM,MAAA,EAAQ,IAAI,MAAA,EAAY;AAAA,EAClD,EAAE,KAAA,EAAO,QAAA,EAAY,IAAA,EAAM,OAAA,EAAS,IAAI,OAAA,EAAc;AAAA,EACtD;AACF,CAAA;AA+BO,IAAM,sBAAA,GAAN,cAAqC,UAAA,CAAwB;AAAA,EAClE,OAAgB,MAAA,GAAS;AAAA,IACvB,MAAA,EAAQ,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACpC,SAAA,EAAW,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,CAAA,EAAE;AAAA,IACtC,YAAA,EAAc,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,GAAA;AAAO,GAChD;AAAA,EAMS,OAAA,GAAU,IAAI,WAAA,EAAY;AAAA;AAAA,EAE1B,UAAU,IAAI,kBAAA,CAAmB,MAAM,IAAA,CAAK,iBAAiB,CAAA;AAAA,EAC7D,eAAe,IAAI,gBAAA,CAAiB,MAAM,IAAA,CAAK,iBAAiB,CAAA;AAAA;AAAA,EAGzE,YAAY,MAAA,CAAO,GAAA;AAAA;AAAA,EAEnB,aAAA,GAAgB,EAAA;AAAA,EAEP,OAAA,GAAgB;AACvB,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,aAAa,QAAA,EAAS;AAK3B,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,YAAY,MAAM,UAAA,EAAY;AAC1D,MAAA,IAAA,CAAK,aAAA,GAAA,CAAiB,IAAA,CAAK,OAAA,CAAQ,WAAA,IAAe,IAAI,IAAA,EAAK;AAAA,IAC7D;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,KAAK,KAAA,CAAM,IAAA,CAAK,QAAQ,YAAA,CAAa,UAAU,KAAK,EAAE,CAAA;AACvE,IAAA,IAAI,MAAA,CAAO,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA,EAAG;AAClC,IAAA,IAAA,CAAK,SAAA,EAAU;AAAA,EACjB;AAAA,EAES,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,QAAQ,MAAA,EAAO;AACpB,IAAA,IAAA,CAAK,aAAa,UAAA,EAAW;AAC7B,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AAAA,EACxB;AAAA;AAAA,EAGA,kBAAA,GAA2B;AACzB,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AAAA,EACxB;AAAA;AAAA,EAGA,qBAAA,GAA8B;AAC5B,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AAAA,EACxB;AAAA;AAAA,EAGA,wBAAA,GAAiC;AAC/B,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,eAAA,GAAwB;AACtB,IAAA,IAAI,MAAA,CAAO,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA,EAAG;AAClC,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,SAAA,EAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,eAAA,GAAwB;AACtB,IAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AACzB,IAAA,IAAA,CAAK,OAAA,CAAQ,cAAc,IAAA,CAAK,aAAA;AAChC,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,YAAY,CAAA;AAAA,EAC3C;AAAA;AAAA,EAGA,SAAA,GAAkB;AAChB,IAAA,MAAM,SAAA,GAAY,KAAK,qBAAA,EAAsB;AAC7C,IAAA,IAAI,cAAc,IAAA,EAAM;AACtB,MAAA,IAAA,CAAK,QAAQ,GAAA,CAAI,MAAM,IAAA,CAAK,SAAA,IAAa,SAAS,CAAA;AAAA,IACpD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,qBAAA,GAAuC;AACrC,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI;AAC1C,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,OAAO,CAAA,GAAI,GAAA;AAEvC,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,CAAC,MAAM,UAAA,GAAa,CAAA,CAAE,KAAK,CAAA,IAAK,UAAA;AAGzD,IAAA,MAAM,SAAA,GAAY,MAAM,IAAA,KAAS,QAAA,IAAY,MAAM,IAAA,KAAS,QAAA,GAAW,MAAS,KAAA,CAAM,EAAA;AACtF,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,CAAI,IAAA,CAAK,mBAAmB,IAAA,CAAK,GAAA,CAAI,SAAA,EAAW,KAAU,CAAC,CAAA;AAKlF,IAAA,IAAI,KAAK,cAAA,GAAiB,CAAA,IAAK,cAAc,IAAA,CAAK,cAAA,IAAkB,KAAK,aAAA,EAAe;AACtF,MAAA,IAAA,CAAK,OAAA,CAAQ,cAAc,IAAA,CAAK,aAAA;AAChC,MAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,YAAA,EAAc,UAAU,CAAA;AAMlD,MAAA,IAAI,OAAA,IAAW,GAAG,OAAO,IAAA;AACzB,MAAA,OAAO,KAAK,GAAA,CAAI,SAAA,EAAW,UAAU,IAAA,CAAK,cAAA,GAAiB,MAAO,CAAC,CAAA;AAAA,IACrE;AAEA,IAAA,MAAM,YAAY,IAAA,CAAK,UAAA;AAGvB,IAAA,IAAI,SAAA,KAAc,MAAM,OAAO,IAAA;AAC/B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,MAAM,EAAE,CAAA;AAC3C,IAAA,IAAA,CAAK,QAAQ,WAAA,GAAc,SAAA,CAAU,MAAA,CAAO,KAAA,EAAO,MAAM,IAAI,CAAA;AAC7D,IAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,YAAA,EAAc,UAAU,CAAA;AAClD,IAAA,OAAO,SAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,UAAA,GAA6C;AAC/C,IAAA,IAAI;AACF,MAAA,OAAO,IAAI,KAAK,kBAAA,CAAmB,IAAA,CAAK,SAAS,EAAE,OAAA,EAAS,QAAQ,CAAA;AAAA,IACtE,CAAA,CAAA,MAAQ;AAGN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,OAAA,GAA8B;AAChC,IAAA,OAAO,IAAA,CAAK,eAAe,IAAA,CAAK,OAAA,CAAQ,QAAQ,QAAQ,CAAA,EAAG,YAAA,CAAa,MAAM,CAAA,IAAK,MAAA;AAAA,EACrF;AACF","file":"relative_time_controller.js","sourcesContent":["/**\n * Runs a controller's \"return to the initial state\" pass just before Turbo\n * caches the page.\n *\n * **`disconnect()` cannot do this job, for two independent reasons.** Turbo\n * queues the clone from this event rather than taking it here, and the body swap\n * that runs the controller's `disconnect()` is queued separately — so which of\n * the two lands first is not something a controller can rely on, and a rewind\n * written in `disconnect()` may reach only the DOM being thrown away. In the\n * other direction, `disconnect()` also fires on an in-page move (Stimulus tears\n * down and reconnects the same element), where rewinding would wipe a\n * legitimately in-progress interaction — a spinner mid-load would vanish. One\n * timing is unreliable, the other is too eager; `turbo:before-cache` is the only\n * point that is exactly \"the page is about to be frozen\".\n *\n * Scope is the subscription only: registering on `activate()`, unregistering on\n * `deactivate()`, and one shared document listener no matter how many instances\n * are live. *What* to return to its initial state — which `data-state`, which\n * `hidden`, which `aria-busy` — stays in the controller, because no two\n * consumers answer it the same way (the `MicrotaskCoalescer` split).\n *\n * **Rewind state, not appearance.** The pass writes attributes the controller\n * itself owns; the visual result of those attributes is the consumer's CSS, and\n * a library that reached for style or class names would be guessing at markup\n * it does not own.\n *\n * Both entry points are idempotent, so the lifecycle hooks can call them\n * unconditionally: a second `activate()` does not double-subscribe and does not\n * make the callback run twice, and `deactivate()` on an instance that never\n * subscribed is a no-op.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #beforeCache = new BeforeCacheReset(() => this.#rewind());\n *\n * connect() { this.#beforeCache.activate(); }\n * disconnect() { this.#beforeCache.deactivate(); }\n * ```\n */\nexport class BeforeCacheReset {\n /** Every subscribed instance, iterated by the one shared document listener. */\n static readonly #subscribers = new Set<BeforeCacheReset>();\n\n /** The shared listener; installed while at least one instance is subscribed. */\n static readonly #onBeforeCache = (): void => {\n for (const subscriber of BeforeCacheReset.#subscribers) subscriber.#rewind();\n };\n\n readonly #rewind: () => void;\n\n /** @param rewind - the pass that returns this controller's state to its initial form. */\n constructor(rewind: () => void) {\n this.#rewind = rewind;\n }\n\n /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */\n activate(): void {\n const first = BeforeCacheReset.#subscribers.size === 0;\n BeforeCacheReset.#subscribers.add(this);\n if (first) {\n document.addEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n }\n\n /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */\n deactivate(): void {\n BeforeCacheReset.#subscribers.delete(this);\n if (BeforeCacheReset.#subscribers.size > 0) return;\n document.removeEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n}\n","/**\n * Collapses many target callbacks from one DOM mutation into a single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element, so\n * replacing a list of N options delivers N callbacks — but the useful unit of\n * work is \"reconcile against the DOM that resulted\", once, after the batch has\n * settled. Every controller that owns a reconcilable target set needs the same\n * shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers the *initial* target callbacks\n * ahead of `connect()`. Reconciling there would compute a fallback against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { BeforeCacheReset } from \"../utils/before_cache_reset\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\n\n/** A time scale: the upper bound (seconds) it covers and its `Intl` unit/divisor. */\ninterface TimeScale {\n limit: number;\n unit: Intl.RelativeTimeFormatUnit;\n ms: number;\n}\n\n/** Coarsest scale, used as the fallback for anything a year or older. */\nconst YEAR_SCALE: TimeScale = { limit: Number.POSITIVE_INFINITY, unit: \"year\", ms: 31_557_600_000 };\n\n/** Boundaries (in seconds) and the `Intl` unit/divisor used at each scale. */\nconst UNITS: readonly TimeScale[] = [\n { limit: 60, unit: \"second\", ms: 1000 },\n { limit: 3600, unit: \"minute\", ms: 60_000 },\n { limit: 86_400, unit: \"hour\", ms: 3_600_000 },\n { limit: 604_800, unit: \"day\", ms: 86_400_000 },\n { limit: 2_629_800, unit: \"week\", ms: 604_800_000 },\n { limit: 31_557_600, unit: \"month\", ms: 2_629_800_000 },\n YEAR_SCALE,\n];\n\n/**\n * Headless relative-time behavior: renders an absolute timestamp as \"3 minutes\n * ago\" / \"in 2 days\" and keeps it fresh. No dedicated APG pattern; it follows\n * the HTML `<time>` semantics.\n *\n * Markup contract (identifier: `stimeo--relative-time`):\n * <time data-controller=\"stimeo--relative-time\"\n * datetime=\"2026-05-30T12:00:00+09:00\" title=\"2026-05-30 12:00\"\n * data-stimeo--relative-time-locale-value=\"ja\">2026-05-30 12:00</time>\n *\n * Computes the difference from `datetime` to now and formats it with\n * `Intl.RelativeTimeFormat` (a browser standard — no added dependency). The\n * polling interval widens as the timestamp ages (seconds → minutes → hours →\n * days). Past a `threshold`, it falls back to the authored absolute text.\n *\n * @remarks\n * Behavior only. The machine-readable `datetime` attribute is left untouched\n * while only the visible text updates, and the element is intentionally **not**\n * a live region (silent updates, no announcement interruptions). The polling\n * timer is owned by {@link SafeTimeout} and torn down on `disconnect()` (Turbo\n * navigation included).\n *\n * Render inputs are followed at runtime: a morph that swaps `locale`, `threshold`,\n * or `tickInterval` on the live element repaints through one coalesced pass\n * ({@link MicrotaskCoalescer}) rather than leaving the reading frozen. The authored\n * absolute text goes back on the element via {@link BeforeCacheReset} before Turbo\n * caches the page — a snapshot taken mid-render would carry the relative form\n * alone, and that is a form `connect()` must not adopt as the fallback.\n */\nexport class RelativeTimeController extends Controller<HTMLElement> {\n static override values = {\n locale: { type: String, default: \"\" },\n threshold: { type: Number, default: 0 },\n tickInterval: { type: Number, default: 60_000 },\n };\n\n declare localeValue: string;\n declare thresholdValue: number;\n declare tickIntervalValue: number;\n\n readonly #timers = new SafeTimeout();\n /** Collapses a morph that swaps several render inputs at once into one repaint. */\n readonly #resync = new MicrotaskCoalescer(() => this.#resyncToValues());\n readonly #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());\n\n /** Epoch ms parsed from `datetime`; `NaN` when absent or invalid. */\n #targetMs = Number.NaN;\n /** The authored absolute text, restored when the threshold fallback kicks in. */\n #absoluteText = \"\";\n\n override connect(): void {\n this.#resync.activate();\n this.#beforeCache.activate();\n // Don't adopt already-rendered relative text as the absolute fallback: on a\n // re-connect against a tree that still holds the live \"3 minutes ago\" text, that\n // relative string would otherwise become `#absoluteText`. Only read the authored\n // textContent before the element has been rendered to a relative form.\n if (this.element.getAttribute(\"data-state\") !== \"relative\") {\n this.#absoluteText = (this.element.textContent ?? \"\").trim();\n }\n this.#targetMs = Date.parse(this.element.getAttribute(\"datetime\") ?? \"\");\n if (Number.isNaN(this.#targetMs)) return;\n this.#schedule();\n }\n\n override disconnect(): void {\n this.#resync.cancel();\n this.#beforeCache.deactivate();\n this.#timers.clearAll();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `locale` at runtime. */\n localeValueChanged(): void {\n this.#resync.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `threshold` at runtime. */\n thresholdValueChanged(): void {\n this.#resync.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `tickInterval` at runtime. */\n tickIntervalValueChanged(): void {\n this.#resync.schedule();\n }\n\n /**\n * Renders against the current Values and re-arms the poll from now.\n *\n * Render only: it emits no event, and clearing first keeps the single self-arming\n * timer single — scheduling on top of a pending one would double the poll rate for\n * the rest of the session. A stamp whose `datetime` never parsed has nothing to\n * render, and one that already reached its terminal fallback simply renders it\n * again and stops.\n */\n #resyncToValues(): void {\n if (Number.isNaN(this.#targetMs)) return;\n this.#timers.clearAll();\n this.#schedule();\n }\n\n /**\n * Restores the authored absolute text and the pre-render state for the snapshot\n * Turbo is about to take, leaving the live page's poll timer alone.\n *\n * With no authored text held there is nothing to restore, and `data-state` has to\n * stay as it is: that marker is what tells the next `connect()` the visible text is\n * a rendered relative form rather than an absolute fallback to hold on to.\n */\n #rewindForCache(): void {\n if (!this.#absoluteText) return;\n this.element.textContent = this.#absoluteText;\n this.element.removeAttribute(\"data-state\");\n }\n\n /** Renders the current representation and reschedules unless polling can stop. */\n #schedule(): void {\n const nextDelay = this.#applyAndComputeDelay();\n if (nextDelay !== null) {\n this.#timers.set(() => this.#schedule(), nextDelay);\n }\n }\n\n /**\n * Updates the visible text and returns the next poll delay (ms), or `null` when\n * polling can stop: a *past* timestamp that fell back to the absolute text can\n * never leave it again, and a locale the runtime rejects has nothing to render\n * until that value is corrected.\n */\n #applyAndComputeDelay(): number | null {\n const deltaMs = this.#targetMs - Date.now();\n const absSeconds = Math.abs(deltaMs) / 1000;\n\n const scale = UNITS.find((u) => absSeconds < u.limit) ?? YEAR_SCALE;\n // Poll no finer than the configured minimum; widen for coarser units so an\n // hours-old stamp is not re-rendered every minute.\n const unitFloor = scale.unit === \"second\" || scale.unit === \"minute\" ? 60_000 : scale.ms;\n const nextDelay = Math.max(this.tickIntervalValue, Math.min(unitFloor, 86_400_000));\n\n // Only switch to the absolute fallback when we actually hold authored absolute\n // text; otherwise (e.g. it could not be recovered after a morph) keep rendering\n // the relative form rather than blanking the element.\n if (this.thresholdValue > 0 && absSeconds >= this.thresholdValue && this.#absoluteText) {\n this.element.textContent = this.#absoluteText;\n this.element.setAttribute(\"data-state\", \"absolute\");\n // A past stamp only ages further, so its fallback is final and polling stops. A\n // future one moves back under the threshold, so keep polling and land 1ms past\n // the crossing: the poll cadence is far coarser than the instant the fallback\n // stops being the correct representation, and the floor does not apply to a hop\n // that exists to leave the fallback rather than to refresh a reading.\n if (deltaMs <= 0) return null;\n return Math.min(nextDelay, deltaMs - this.thresholdValue * 1000 + 1);\n }\n\n const formatter = this.#formatter;\n // Nothing to render in a locale the runtime rejects: leaving the element as\n // authored beats blanking it or guessing at another language.\n if (formatter === null) return null;\n const value = Math.round(deltaMs / scale.ms);\n this.element.textContent = formatter.format(value, scale.unit);\n this.element.setAttribute(\"data-state\", \"relative\");\n return nextDelay;\n }\n\n /**\n * A `RelativeTimeFormat` for the resolved locale (`numeric: \"auto\"`), or `null`\n * when the runtime rejects that locale.\n */\n get #formatter(): Intl.RelativeTimeFormat | null {\n try {\n return new Intl.RelativeTimeFormat(this.#locale, { numeric: \"auto\" });\n } catch {\n // A malformed locale must not break the page: the authored absolute text stays\n // as the graceful fallback, and a corrected value renders on the next pass.\n return null;\n }\n }\n\n /** Locale precedence: the value, then the nearest `lang` up the ancestor chain. */\n get #locale(): string | undefined {\n return this.localeValue || this.element.closest(\"[lang]\")?.getAttribute(\"lang\") || undefined;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/before_cache_reset.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/safe_timeout.ts","../../src/controllers/relative_time_controller.ts"],"names":[],"mappings":"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","file":"relative_time_controller.js","sourcesContent":["/**\n * Runs a controller's \"return to the initial state\" pass just before Turbo\n * caches the page.\n *\n * **`disconnect()` cannot do this job, for two independent reasons.** Turbo\n * queues the clone from this event rather than taking it here, and the body swap\n * that runs the controller's `disconnect()` is queued separately — so which of\n * the two lands first is not something a controller can rely on, and a rewind\n * written in `disconnect()` may reach only the DOM being thrown away. In the\n * other direction, `disconnect()` also fires on an in-page move (Stimulus tears\n * down and reconnects the same element), where rewinding would wipe a\n * legitimately in-progress interaction — a spinner mid-load would vanish. One\n * timing is unreliable, the other is too eager; `turbo:before-cache` is the only\n * point that is exactly \"the page is about to be frozen\".\n *\n * Scope is the subscription only: registering on `activate()`, unregistering on\n * `deactivate()`, and one shared document listener no matter how many instances\n * are live. *What* to return to its initial state — which `data-state`, which\n * `hidden`, which `aria-busy` — stays in the controller, because no two\n * consumers answer it the same way (the `MicrotaskCoalescer` split).\n *\n * **Rewind state, not appearance.** The pass writes attributes the controller\n * itself owns; the visual result of those attributes is the consumer's CSS, and\n * a library that reached for style or class names would be guessing at markup\n * it does not own.\n *\n * Both entry points are idempotent, so the lifecycle hooks can call them\n * unconditionally: a second `activate()` does not double-subscribe and does not\n * make the callback run twice, and `deactivate()` on an instance that never\n * subscribed is a no-op.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #beforeCache = new BeforeCacheReset(() => this.#rewind());\n *\n * connect() { this.#beforeCache.activate(); }\n * disconnect() { this.#beforeCache.deactivate(); }\n * ```\n */\nexport class BeforeCacheReset {\n /** Every subscribed instance, iterated by the one shared document listener. */\n static readonly #subscribers = new Set<BeforeCacheReset>();\n\n /** The shared listener; installed while at least one instance is subscribed. */\n static readonly #onBeforeCache = (): void => {\n for (const subscriber of BeforeCacheReset.#subscribers) subscriber.#rewind();\n };\n\n readonly #rewind: () => void;\n\n /** @param rewind - the pass that returns this controller's state to its initial form. */\n constructor(rewind: () => void) {\n this.#rewind = rewind;\n }\n\n /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */\n activate(): void {\n const first = BeforeCacheReset.#subscribers.size === 0;\n BeforeCacheReset.#subscribers.add(this);\n if (first) {\n document.addEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n }\n\n /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */\n deactivate(): void {\n BeforeCacheReset.#subscribers.delete(this);\n if (BeforeCacheReset.#subscribers.size > 0) return;\n document.removeEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { BeforeCacheReset } from \"../utils/before_cache_reset\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\n\n/** A time scale: the upper bound (seconds) it covers and its `Intl` unit/divisor. */\ninterface TimeScale {\n limit: number;\n unit: Intl.RelativeTimeFormatUnit;\n ms: number;\n}\n\n/** Coarsest scale, used as the fallback for anything a year or older. */\nconst YEAR_SCALE: TimeScale = { limit: Number.POSITIVE_INFINITY, unit: \"year\", ms: 31_557_600_000 };\n\n/** Boundaries (in seconds) and the `Intl` unit/divisor used at each scale. */\nconst UNITS: readonly TimeScale[] = [\n { limit: 60, unit: \"second\", ms: 1000 },\n { limit: 3600, unit: \"minute\", ms: 60_000 },\n { limit: 86_400, unit: \"hour\", ms: 3_600_000 },\n { limit: 604_800, unit: \"day\", ms: 86_400_000 },\n { limit: 2_629_800, unit: \"week\", ms: 604_800_000 },\n { limit: 31_557_600, unit: \"month\", ms: 2_629_800_000 },\n YEAR_SCALE,\n];\n\n/**\n * Headless relative-time behavior: renders an absolute timestamp as \"3 minutes\n * ago\" / \"in 2 days\" and keeps it fresh. No dedicated APG pattern; it follows\n * the HTML `<time>` semantics.\n *\n * Markup contract (identifier: `stimeo--relative-time`):\n * <time data-controller=\"stimeo--relative-time\"\n * datetime=\"2026-05-30T12:00:00+09:00\" title=\"2026-05-30 12:00\"\n * data-stimeo--relative-time-locale-value=\"ja\">2026-05-30 12:00</time>\n *\n * Computes the difference from `datetime` to now and formats it with\n * `Intl.RelativeTimeFormat` (a browser standard — no added dependency). The\n * polling interval widens as the timestamp ages (seconds → minutes → hours →\n * days). Past a `threshold`, it falls back to the authored absolute text.\n *\n * @remarks\n * Behavior only. The machine-readable `datetime` attribute is left untouched\n * while only the visible text updates, and the element is intentionally **not**\n * a live region (silent updates, no announcement interruptions). The polling\n * timer is owned by {@link SafeTimeout} and torn down on `disconnect()` (Turbo\n * navigation included).\n *\n * Render inputs are followed at runtime: a morph that swaps `locale`, `threshold`,\n * or `tickInterval` on the live element repaints through one coalesced pass\n * ({@link MicrotaskCoalescer}) rather than leaving the reading frozen. The authored\n * absolute text goes back on the element via {@link BeforeCacheReset} before Turbo\n * caches the page — a snapshot taken mid-render would carry the relative form\n * alone, and that is a form `connect()` must not adopt as the fallback.\n */\nexport class RelativeTimeController extends Controller<HTMLElement> {\n static override values = {\n locale: { type: String, default: \"\" },\n threshold: { type: Number, default: 0 },\n tickInterval: { type: Number, default: 60_000 },\n };\n\n declare localeValue: string;\n declare thresholdValue: number;\n declare tickIntervalValue: number;\n\n readonly #timers = new SafeTimeout();\n /** Collapses a morph that swaps several render inputs at once into one repaint. */\n readonly #resync = new MicrotaskCoalescer(() => this.#resyncToValues());\n readonly #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());\n\n /** Epoch ms parsed from `datetime`; `NaN` when absent or invalid. */\n #targetMs = Number.NaN;\n /** The authored absolute text, restored when the threshold fallback kicks in. */\n #absoluteText = \"\";\n\n override connect(): void {\n this.#resync.activate();\n this.#beforeCache.activate();\n // Don't adopt already-rendered relative text as the absolute fallback: on a\n // re-connect against a tree that still holds the live \"3 minutes ago\" text, that\n // relative string would otherwise become `#absoluteText`. Only read the authored\n // textContent before the element has been rendered to a relative form.\n if (this.element.getAttribute(\"data-state\") !== \"relative\") {\n this.#absoluteText = (this.element.textContent ?? \"\").trim();\n }\n this.#targetMs = Date.parse(this.element.getAttribute(\"datetime\") ?? \"\");\n if (Number.isNaN(this.#targetMs)) return;\n this.#schedule();\n }\n\n override disconnect(): void {\n this.#resync.cancel();\n this.#beforeCache.deactivate();\n this.#timers.clearAll();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `locale` at runtime. */\n localeValueChanged(): void {\n this.#resync.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `threshold` at runtime. */\n thresholdValueChanged(): void {\n this.#resync.schedule();\n }\n\n /** Repaints when application code (or a Turbo morph) changes `tickInterval` at runtime. */\n tickIntervalValueChanged(): void {\n this.#resync.schedule();\n }\n\n /**\n * Renders against the current Values and re-arms the poll from now.\n *\n * Render only: it emits no event, and clearing first keeps the single self-arming\n * timer single — scheduling on top of a pending one would double the poll rate for\n * the rest of the session. A stamp whose `datetime` never parsed has nothing to\n * render, and one that already reached its terminal fallback simply renders it\n * again and stops.\n */\n #resyncToValues(): void {\n if (Number.isNaN(this.#targetMs)) return;\n this.#timers.clearAll();\n this.#schedule();\n }\n\n /**\n * Restores the authored absolute text and the pre-render state for the snapshot\n * Turbo is about to take, leaving the live page's poll timer alone.\n *\n * With no authored text held there is nothing to restore, and `data-state` has to\n * stay as it is: that marker is what tells the next `connect()` the visible text is\n * a rendered relative form rather than an absolute fallback to hold on to.\n */\n #rewindForCache(): void {\n if (!this.#absoluteText) return;\n this.element.textContent = this.#absoluteText;\n this.element.removeAttribute(\"data-state\");\n }\n\n /** Renders the current representation and reschedules unless polling can stop. */\n #schedule(): void {\n const nextDelay = this.#applyAndComputeDelay();\n if (nextDelay !== null) {\n this.#timers.set(() => this.#schedule(), nextDelay);\n }\n }\n\n /**\n * Updates the visible text and returns the next poll delay (ms), or `null` when\n * polling can stop: a *past* timestamp that fell back to the absolute text can\n * never leave it again, and a locale the runtime rejects has nothing to render\n * until that value is corrected.\n *\n * @stimeoRenderRoot\n */\n #applyAndComputeDelay(): number | null {\n const deltaMs = this.#targetMs - Date.now();\n const absSeconds = Math.abs(deltaMs) / 1000;\n\n const scale = UNITS.find((u) => absSeconds < u.limit) ?? YEAR_SCALE;\n // Poll no finer than the configured minimum; widen for coarser units so an\n // hours-old stamp is not re-rendered every minute.\n const unitFloor = scale.unit === \"second\" || scale.unit === \"minute\" ? 60_000 : scale.ms;\n const nextDelay = Math.max(this.tickIntervalValue, Math.min(unitFloor, 86_400_000));\n\n // Only switch to the absolute fallback when we actually hold authored absolute\n // text; otherwise (e.g. it could not be recovered after a morph) keep rendering\n // the relative form rather than blanking the element.\n if (this.thresholdValue > 0 && absSeconds >= this.thresholdValue && this.#absoluteText) {\n this.element.textContent = this.#absoluteText;\n this.element.setAttribute(\"data-state\", \"absolute\");\n // A past stamp only ages further, so its fallback is final and polling stops. A\n // future one moves back under the threshold, so keep polling and land 1ms past\n // the crossing: the poll cadence is far coarser than the instant the fallback\n // stops being the correct representation, and the floor does not apply to a hop\n // that exists to leave the fallback rather than to refresh a reading.\n if (deltaMs <= 0) return null;\n return Math.min(nextDelay, deltaMs - this.thresholdValue * 1000 + 1);\n }\n\n const formatter = this.#formatter;\n // Nothing to render in a locale the runtime rejects: leaving the element as\n // authored beats blanking it or guessing at another language.\n if (formatter === null) return null;\n const value = Math.round(deltaMs / scale.ms);\n this.element.textContent = formatter.format(value, scale.unit);\n this.element.setAttribute(\"data-state\", \"relative\");\n return nextDelay;\n }\n\n /**\n * A `RelativeTimeFormat` for the resolved locale (`numeric: \"auto\"`), or `null`\n * when the runtime rejects that locale.\n */\n get #formatter(): Intl.RelativeTimeFormat | null {\n try {\n return new Intl.RelativeTimeFormat(this.#locale, { numeric: \"auto\" });\n } catch {\n // A malformed locale must not break the page: the authored absolute text stays\n // as the graceful fallback, and a corrected value renders on the next pass.\n return null;\n }\n }\n\n /** Locale precedence: the value, then the nearest `lang` up the ancestor chain. */\n get #locale(): string | undefined {\n return this.localeValue || this.element.closest(\"[lang]\")?.getAttribute(\"lang\") || undefined;\n }\n}\n"]}
@@ -17,7 +17,7 @@ import { Controller } from '@hotwired/stimulus';
17
17
  * own, the viewport is made keyboard-scrollable (`tabindex="0"`, plus `role="region"`
18
18
  * when it already has an accessible name). Scroll position is published as
19
19
  * `data-scroll` (`start`/`middle`/`end`), overflow as `data-overflow`, and progress
20
- * as `--stimeo-scroll-progress` (0–1) so consumer CSS can draw scroll shadows.
20
+ * as `--stimeo--scroll-progress` (0–1) so consumer CSS can draw scroll shadows.
21
21
  *
22
22
  * @remarks
23
23
  * Behavior only. The `scroll` listener and {@link LayoutObserver} (element +
@@ -165,7 +165,7 @@ var ScrollAreaController = class extends Controller {
165
165
  this.#syncKeyboardReach(vp, overflowing);
166
166
  const { position, progress } = this.#measurePosition(vp);
167
167
  this.element.setAttribute("data-scroll", position);
168
- this.element.style.setProperty("--stimeo-scroll-progress", String(progress));
168
+ this.element.style.setProperty("--stimeo--scroll-progress", String(progress));
169
169
  const edge = position === "start" ? "start" : position === "end" ? "end" : null;
170
170
  if (overflowing && edge && edge !== this.#lastEdge) {
171
171
  this.#lastEdge = edge;