stimeo-ui 0.5.0 → 0.7.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.
- package/CHANGELOG.md +178 -0
- package/dist/controllers/alert_dialog_controller.d.ts +2 -0
- package/dist/controllers/alert_dialog_controller.js +75 -4
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.d.ts +1 -3
- package/dist/controllers/aspect_ratio_controller.js +19 -11
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +2 -0
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.d.ts +36 -15
- package/dist/controllers/avatar_controller.js +237 -40
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +2 -0
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +2 -0
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +2 -0
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +12 -3
- package/dist/controllers/carousel_controller.js +85 -9
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +52 -20
- package/dist/controllers/character_counter_controller.js +338 -63
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +34 -5
- package/dist/controllers/checkbox_controller.js +136 -25
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +10 -1
- package/dist/controllers/color_picker_controller.js +35 -9
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +2 -0
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +2 -0
- package/dist/controllers/command_palette_controller.js +75 -4
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +42 -14
- package/dist/controllers/conditional_fields_controller.js +345 -51
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +2 -0
- package/dist/controllers/confirm_controller.js +75 -4
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +2 -0
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +4 -0
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +2 -0
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +3 -0
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +23 -4
- package/dist/controllers/date_range_picker_controller.js +157 -30
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.js +75 -4
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +56 -24
- package/dist/controllers/direct_upload_controller.js +201 -45
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +14 -6
- package/dist/controllers/dirty_form_controller.js +192 -29
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +2 -0
- package/dist/controllers/dismissible_controller.js +83 -18
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.js +75 -4
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +2 -0
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +9 -1
- package/dist/controllers/file_dropzone_controller.js +26 -3
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +2 -0
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +4 -0
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +4 -3
- package/dist/controllers/focus_controller.js +75 -4
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.d.ts +50 -10
- package/dist/controllers/form_field_controller.js +280 -62
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +10 -8
- package/dist/controllers/form_validation_controller.js +208 -83
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +2 -0
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +2 -0
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +2 -2
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +5 -3
- package/dist/controllers/idle_controller.js +27 -5
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +2 -0
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +2 -0
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +2 -0
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +2 -0
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +2 -0
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +5 -3
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +2 -0
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +48 -12
- package/dist/controllers/multi_select_controller.js +460 -151
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +2 -0
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +2 -0
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -6
- package/dist/controllers/number_input_controller.js +317 -51
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +3 -0
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +2 -0
- package/dist/controllers/overflow_menu_controller.js +6 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +2 -0
- package/dist/controllers/pagination_controller.js +35 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +2 -0
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +5 -3
- package/dist/controllers/password_strength_controller.js +20 -2
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +33 -19
- package/dist/controllers/persist_controller.js +449 -120
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +3 -0
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +2 -2
- package/dist/controllers/popover_controller.js +77 -4
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +3 -2
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +2 -0
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +3 -0
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +44 -15
- package/dist/controllers/radio_group_controller.js +540 -56
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +38 -31
- package/dist/controllers/rating_controller.js +276 -89
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +2 -0
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +2 -0
- package/dist/controllers/resizable_controller.js +33 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +6 -0
- package/dist/controllers/roving_controller.js +60 -5
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +25 -11
- package/dist/controllers/scroll_area_controller.js +557 -125
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
- package/dist/controllers/scroll_visibility_controller.js +33 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +2 -0
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +54 -11
- package/dist/controllers/separator_controller.js +354 -38
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.js +83 -10
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +2 -0
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +2 -0
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +2 -0
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +2 -0
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +2 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +2 -0
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +94 -38
- package/dist/controllers/submit_once_controller.js +399 -121
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +2 -0
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +43 -11
- package/dist/controllers/tags_input_controller.js +356 -120
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +2 -0
- package/dist/controllers/theme_controller.js +8 -6
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +44 -13
- package/dist/controllers/time_picker_controller.js +296 -107
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +4 -0
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +41 -13
- package/dist/controllers/toggle_group_controller.js +378 -55
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +5 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +2 -2
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +4 -3
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +2 -0
- package/dist/controllers/tree_view_controller.js +7 -4
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +4963 -1581
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +123 -6
- package/dist/inspector/cli.js +212 -18
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +274 -50
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +15 -15
- package/dist/inspector/manifest.json +602 -80
- package/dist/positioning/index.d.ts +4 -2
- package/dist/positioning/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/controllers/time_picker_controller.ts"],"names":["text"],"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;;;ACnGA,IAAM,EAAA,GAAK,CAAA;AACX,IAAM,EAAA,GAAK,CAAA;AAiCJ,IAAM,oBAAA,GAAN,cAAmC,UAAA,CAAwB;AAAA,EAChE,OAAgB,OAAA,GAAU,CAAC,SAAA,EAAW,OAAO,CAAA;AAAA,EAC7C,OAAgB,MAAA,GAAS;AAAA,IACvB,SAAA,EAAW,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACvC,IAAA,EAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,CAAA,EAAE;AAAA,IACjC,OAAA,EAAS,EAAE,IAAA,EAAM,OAAA,EAAS,SAAS,KAAA,EAAM;AAAA,IACzC,IAAA,EAAM,EAAE,IAAA,EAAM,OAAA,EAAS,SAAS,IAAA;AAAK,GACvC;AAAA,EACA,OAAO,OAAA,GAAU,CAAC,WAAW,CAAA;AAAA,EAC7B,OAAO,MAAA,GAAS,CAAC,QAAQ,CAAA;AAAA;AAAA,EAWzB,MAAA,GAAsC,EAAE,IAAA,EAAM,CAAA,EAAG,QAAQ,CAAA,EAAG,MAAA,EAAQ,CAAA,EAAG,QAAA,EAAU,EAAA,EAAG;AAAA;AAAA,EAEpF,WAAA,GAAc,EAAA;AAAA,EACd,YAAA,GAAmC,IAAA;AAAA;AAAA,EAEnC,UAAA,GAAa,EAAA;AAAA;AAAA,EAGJ,OAAA,GAAgB;AACvB,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,cAAA,EAAgB;AACzC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AACjC,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,MAAM,GAAA,GAAM,MAAA,CAAO,OAAA,CAAQ,YAAA,CAAa,eAAe,CAAC,CAAA;AACxD,MAAA,MAAM,EAAE,GAAA,EAAK,GAAA,EAAI,GAAI,IAAA,CAAK,QAAQ,IAAI,CAAA;AAGtC,MAAA,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,GAAI,MAAA,CAAO,SAAS,GAAG,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,KAAK,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,GAAG,CAAC,CAAA,GAAI,GAAA;AAAA,IACjF;AACA,IAAA,KAAA,MAAW,OAAA,IAAW,IAAA,CAAK,cAAA,EAAgB,IAAA,CAAK,eAAe,OAAO,CAAA;AACtE,IAAA,IAAA,CAAK,WAAW,KAAK,CAAA;AAAA,EACvB;AAAA;AAAA,EAGA,UAAU,KAAA,EAA4B;AACpC,IAAA,IAAI,oBAAA,CAAqB,KAAK,CAAA,EAAG;AACjC,IAAA,MAAM,OAAA,GAAW,MAAM,MAAA,EAA+B,OAAA;AAAA,MACpD;AAAA,KACF;AACA,IAAA,MAAM,IAAA,GAAO,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,GAAI,IAAA;AAC/C,IAAA,IAAI,CAAC,OAAA,IAAW,CAAC,IAAA,EAAM;AAMvB,IAAA,QAAQ,eAAA,CAAgB,KAAA,CAAM,GAAA,EAAK,IAAA,CAAK,OAAO,CAAA;AAAG,MAChD,KAAK,SAAA;AACH,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA;AAClC,QAAA;AAAA,MACF,KAAK,WAAA;AACH,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAA,CAAK,MAAM,IAAA,EAAM,CAAC,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA;AACnC,QAAA;AAAA,MACF,KAAK,WAAA;AACH,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAA,CAAK,aAAA,CAAc,SAAS,EAAE,CAAA;AAC9B,QAAA;AAAA,MACF,KAAK,YAAA;AACH,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAA,CAAK,aAAA,CAAc,SAAS,CAAC,CAAA;AAC7B,QAAA;AAAA,MACF,KAAK,MAAA;AACH,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAA,CAAK,KAAK,IAAA,EAAM,IAAA,CAAK,OAAA,CAAQ,IAAI,EAAE,GAAG,CAAA;AACtC,QAAA;AAAA,MACF,KAAK,KAAA;AACH,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAA,CAAK,KAAK,IAAA,EAAM,IAAA,CAAK,OAAA,CAAQ,IAAI,EAAE,GAAG,CAAA;AACtC,QAAA;AAAA,MACF;AACE,QAAA,IAAI,SAAA,CAAU,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,EAAG;AAC7B,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,IAAA,CAAK,UAAA,CAAW,OAAA,EAAS,IAAA,EAAM,KAAA,CAAM,GAAG,CAAA;AAAA,QAC1C;AACA,QAAA;AAAA;AAGJ,IAAA,IAAA,CAAK,WAAA,GAAc,EAAA;AACnB,IAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AAAA,EACtB;AAAA;AAAA,EAGA,OAAO,IAAA,EAA2B;AAChC,IAAA,OAAO,IAAA,KAAS,QAAA,GAAW,IAAA,CAAK,SAAA,GAAY,CAAA;AAAA,EAC9C;AAAA;AAAA,EAGA,KAAA,CAAM,MAAmB,KAAA,EAAqB;AAC5C,IAAA,IAAI,SAAS,UAAA,EAAY;AACvB,MAAA,IAAA,CAAK,KAAK,UAAA,EAAY,IAAA,CAAK,OAAO,QAAA,KAAa,EAAA,GAAK,KAAK,EAAE,CAAA;AAC3D,MAAA;AAAA,IACF;AACA,IAAA,MAAM,EAAE,GAAA,EAAK,GAAA,EAAI,GAAI,IAAA,CAAK,QAAQ,IAAI,CAAA;AACtC,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,GAAM,CAAA;AACzB,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,GAAI,KAAA;AAEhC,IAAA,IAAI,GAAA,GAAM,GAAA,IAAO,GAAA,GAAM,GAAA,EAAK;AAC1B,MAAA,IAAI,CAAC,KAAK,SAAA,EAAW;AACnB,QAAA,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,KAAK,GAAA,CAAI,GAAA,EAAK,GAAG,CAAC,CAAC,CAAA;AACjD,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAA,CAAA,CAAc,GAAA,GAAM,GAAA,IAAO,IAAA,GAAQ,QAAQ,IAAA,GAAQ,GAAA;AACzD,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAA,CAAO,GAAA,GAAM,OAAO,IAAI,CAAA;AAC3C,MAAA,IAAA,CAAK,IAAA,CAAK,MAAM,OAAO,CAAA;AACvB,MAAA,IAAA,CAAK,MAAA,CAAO,MAAM,KAAK,CAAA;AACvB,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACrB;AAAA;AAAA,EAGA,MAAA,CAAO,MAAmB,MAAA,EAAsB;AAC9C,IAAA,IAAI,IAAA,KAAS,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,UAAU,MAAM,CAAA;AAAA,SAAA,IACzC,IAAA,KAAS,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,QAAQ,MAAM,CAAA;AAAA,EAEvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,IAAA,CAAK,MAAmB,KAAA,EAAqB;AAC3C,IAAA,MAAM,EAAE,GAAA,EAAK,GAAA,EAAI,GAAI,IAAA,CAAK,QAAQ,IAAI,CAAA;AACtC,IAAA,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,KAAK,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,KAAK,CAAC,CAAA;AACtD,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,cAAA,CAAe,IAAA,CAAK,CAAC,MAAM,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,KAAM,IAAI,CAAA;AACxE,IAAA,IAAI,OAAA,EAAS,IAAA,CAAK,cAAA,CAAe,OAAO,CAAA;AACxC,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AAAA,EACtB;AAAA;AAAA,EAGA,UAAA,CAAW,OAAA,EAAsB,IAAA,EAAmB,KAAA,EAAqB;AACvE,IAAA,IAAI,SAAS,UAAA,EAAY;AACzB,IAAA,IAAI,IAAA,CAAK,YAAA,KAAiB,IAAA,EAAM,IAAA,CAAK,WAAA,GAAc,EAAA;AACnD,IAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AAEpB,IAAA,MAAM,EAAE,GAAA,EAAI,GAAI,IAAA,CAAK,QAAQ,IAAI,CAAA;AACjC,IAAA,MAAM,YAAY,MAAA,CAAO,CAAA,EAAG,KAAK,WAAW,CAAA,EAAG,KAAK,CAAA,CAAE,CAAA;AACtD,IAAA,IAAI,SAAA,IAAa,KAAK,IAAA,CAAK,WAAA,GAAc,GAAG,IAAA,CAAK,WAAW,GAAG,KAAK,CAAA,CAAA;AAAA,cAC1D,WAAA,GAAc,KAAA;AAExB,IAAA,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,WAAW,CAAC,CAAA;AAGxC,IAAA,IAAI,IAAA,CAAK,YAAY,MAAA,IAAU,CAAA,IAAK,OAAO,IAAA,CAAK,WAAW,CAAA,GAAI,EAAA,GAAK,GAAA,EAAK;AACvE,MAAA,IAAA,CAAK,WAAA,GAAc,EAAA;AACnB,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,MAAA,IAAA,CAAK,aAAA,CAAc,SAAS,CAAC,CAAA;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA,EAGA,aAAA,CAAc,SAAsB,SAAA,EAAyB;AAC3D,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,cAAA,CAAe,OAAA,CAAQ,OAAO,CAAA;AACjD,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,cAAA,CAAe,KAAA,GAAQ,SAAS,CAAA;AAClD,IAAA,IAAA,EAAM,KAAA,EAAM;AAAA,EACd;AAAA;AAAA,EAGA,eAAe,OAAA,EAA4B;AACzC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AACjC,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA;AAE9B,IAAA,IAAI,SAAS,UAAA,EAAY;AACvB,MAAA,MAAMA,KAAAA,GAAO,KAAA,KAAU,EAAA,GAAK,IAAA,GAAO,IAAA;AACnC,MAAA,OAAA,CAAQ,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,KAAK,CAAC,CAAA;AACnD,MAAA,OAAA,CAAQ,YAAA,CAAa,kBAAkBA,KAAI,CAAA;AAC3C,MAAA,OAAA,CAAQ,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,EAAE,CAAC,CAAA;AAChD,MAAA,OAAA,CAAQ,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,EAAE,CAAC,CAAA;AAChD,MAAA,OAAA,CAAQ,WAAA,GAAcA,KAAAA;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,GAAA,EAAK,GAAA,EAAI,GAAI,IAAA,CAAK,QAAQ,IAAI,CAAA;AACtC,IAAA,MAAM,OAAO,MAAA,CAAO,KAAK,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAC1C,IAAA,OAAA,CAAQ,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,KAAK,CAAC,CAAA;AACnD,IAAA,OAAA,CAAQ,YAAA,CAAa,kBAAkB,IAAI,CAAA;AAC3C,IAAA,OAAA,CAAQ,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,GAAG,CAAC,CAAA;AACjD,IAAA,OAAA,CAAQ,YAAA,CAAa,eAAA,EAAiB,MAAA,CAAO,GAAG,CAAC,CAAA;AACjD,IAAA,OAAA,CAAQ,WAAA,GAAc,IAAA;AAAA,EACxB;AAAA;AAAA,EAGA,WAAW,MAAA,EAAuB;AAChC,IAAA,MAAM,GAAA,GAAM,KAAK,QAAA,EAAS;AAC1B,IAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,CAAI,GAAG,GAAG,GAAA,CAAI,IAAA,CAAK,MAAA,CAAO,MAAM,CAAC,CAAA;AAChD,IAAA,IAAI,IAAA,CAAK,cAAc,KAAA,CAAM,IAAA,CAAK,IAAI,IAAA,CAAK,MAAA,CAAO,MAAM,CAAC,CAAA;AACzD,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA;AAE5B,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,cAAA,IAAkB,IAAA,CAAK,YAAY,KAAA,KAAU,KAAA;AACvE,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,IAAA,CAAK,YAAY,KAAA,GAAQ,KAAA;AAAA,IAC3B;AACA,IAAA,IAAI,UAAU,YAAA,EAAc;AAG1B,MAAA,IAAA,CAAK,WAAA,CAAY,cAAc,IAAI,KAAA,CAAM,UAAU,EAAE,OAAA,EAAS,IAAA,EAAM,CAAC,CAAA;AAAA,IACvE;AAGA,IAAA,IAAI,MAAA,IAAU,KAAA,KAAU,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,EAAE,MAAA,EAAQ,EAAE,KAAA,EAAM,EAAG,CAAA;AACtF,IAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAAA,EACpB;AAAA;AAAA,EAGA,QAAA,GAAmB;AACjB,IAAA,IAAI,IAAA,CAAK,cAAA,KAAmB,EAAA,EAAI,OAAO,KAAK,MAAA,CAAO,IAAA;AACnD,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAA,CAAO,IAAA,GAAO,EAAA;AAChC,IAAA,OAAO,IAAA,IAAQ,IAAA,CAAK,MAAA,CAAO,QAAA,KAAa,KAAK,EAAA,GAAK,CAAA,CAAA;AAAA,EACpD;AAAA;AAAA,EAGA,QAAQ,IAAA,EAAiD;AACvD,IAAA,QAAQ,IAAA;AAAM,MACZ,KAAK,MAAA;AACH,QAAA,OAAO,IAAA,CAAK,cAAA,KAAmB,EAAA,GAAK,EAAE,GAAA,EAAK,CAAA,EAAG,GAAA,EAAK,EAAA,EAAG,GAAI,EAAE,GAAA,EAAK,CAAA,EAAG,KAAK,EAAA,EAAG;AAAA,MAC9E,KAAK,QAAA;AAAA,MACL,KAAK,QAAA;AACH,QAAA,OAAO,EAAE,GAAA,EAAK,CAAA,EAAG,GAAA,EAAK,EAAA,EAAG;AAAA,MAC3B,KAAK,UAAA;AACH,QAAA,OAAO,EAAE,GAAA,EAAK,EAAA,EAAI,GAAA,EAAK,EAAA,EAAG;AAAA;AAC9B,EACF;AAAA;AAAA,EAGA,QAAQ,OAAA,EAA0C;AAChD,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,YAAA,CAAa,cAAc,CAAA;AAChD,IAAA,IAAI,SAAS,MAAA,IAAU,IAAA,KAAS,YAAY,IAAA,KAAS,QAAA,IAAY,SAAS,UAAA,EAAY;AACpF,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAGA,SAAS,IAAI,KAAA,EAAuB;AAClC,EAAA,OAAO,MAAA,CAAO,KAAK,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AACtC","file":"time_picker_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","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\n\n/** A time segment kind, as declared by `data-segment` on each spinbutton. */\ntype SegmentKind = \"hour\" | \"minute\" | \"second\" | \"meridiem\";\n\n/** Meridiem encoding: 0 = AM, 1 = PM. */\nconst AM = 0;\nconst PM = 1;\n\n/**\n * Headless, accessible **time picker** behavior. Each segment (hour, minute,\n * optional second, optional AM/PM) is an APG **Spinbutton**; the controller\n * composes them into an `HH:MM[:SS]` value mirrored to a hidden field.\n *\n * Markup contract (identifier: `stimeo--time-picker`):\n * <div data-controller=\"stimeo--time-picker\"\n * data-stimeo--time-picker-hour-cycle-value=\"24\"\n * role=\"group\" aria-label=\"Time\">\n * <span role=\"spinbutton\" aria-label=\"Hours\" tabindex=\"0\"\n * aria-valuenow=\"9\" aria-valuemin=\"0\" aria-valuemax=\"23\" aria-valuetext=\"09\"\n * data-segment=\"hour\" data-stimeo--time-picker-target=\"segment\"\n * data-action=\"keydown->stimeo--time-picker#onKeydown\">09</span>\n * <span aria-hidden=\"true\">:</span>\n * <span role=\"spinbutton\" aria-label=\"Minutes\" tabindex=\"0\"\n * data-segment=\"minute\" data-stimeo--time-picker-target=\"segment\"\n * data-action=\"keydown->stimeo--time-picker#onKeydown\">30</span>\n * <input type=\"hidden\" data-stimeo--time-picker-target=\"field\" />\n * </div>\n *\n * @remarks\n * Behavior only — no styling, no locale formatting. Every segment is its own Tab\n * stop (multi-tabstop, *not* roving); `ArrowLeft`/`ArrowRight` are an auxiliary\n * move between segments. `ArrowUp`/`ArrowDown` step the focused segment, wrapping\n * (and, when `wrap` is set, carrying minutes→hours and seconds→minutes). Typing\n * digits enters a value directly and advances after two digits. AM/PM is modeled\n * as a `meridiem` spinbutton that toggles; the hidden field is always 24-hour.\n * A committed field-value change dispatches a bubbling native `change` from\n * that field, while `stimeo--time-picker:change` reports the composed widget\n * state even when no field target is present.\n */\nexport class TimePickerController extends Controller<HTMLElement> {\n static override targets = [\"segment\", \"field\"];\n static override values = {\n hourCycle: { type: Number, default: 24 },\n step: { type: Number, default: 1 },\n seconds: { type: Boolean, default: false },\n wrap: { type: Boolean, default: true },\n };\n static actions = [\"onKeydown\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly segmentTargets: HTMLElement[];\n declare readonly fieldTarget: HTMLInputElement;\n declare readonly hasFieldTarget: boolean;\n declare hourCycleValue: number;\n declare stepValue: number;\n declare secondsValue: boolean;\n declare wrapValue: boolean;\n\n /** Current numeric value per segment kind (hours are the *displayed* hours). */\n #state: Record<SegmentKind, number> = { hour: 0, minute: 0, second: 0, meridiem: AM };\n /** Direct-entry digit buffer and the segment it belongs to. */\n #typeBuffer = \"\";\n #typeSegment: SegmentKind | null = null;\n /** Last composed field value, to suppress duplicate `change` dispatches. */\n #lastValue = \"\";\n\n /** Seeds each segment from its initial `aria-valuenow` and syncs the field. */\n override connect(): void {\n for (const segment of this.segmentTargets) {\n const kind = this.#kindOf(segment);\n if (!kind) continue;\n const now = Number(segment.getAttribute(\"aria-valuenow\"));\n const { min, max } = this.#bounds(kind);\n // Clamp the seeded value so malformed markup (out-of-range aria-valuenow)\n // never propagates into the rendered state or the composed field.\n this.#state[kind] = Number.isFinite(now) ? Math.min(max, Math.max(min, now)) : min;\n }\n for (const segment of this.segmentTargets) this.#renderSegment(segment);\n this.#syncField(false);\n }\n\n /** Handles stepping, inter-segment focus moves, jumps, and direct entry. */\n onKeydown(event: KeyboardEvent): void {\n if (isReservedArrowChord(event)) return;\n const segment = (event.target as HTMLElement | null)?.closest<HTMLElement>(\n \"[data-stimeo--time-picker-target='segment']\",\n );\n const kind = segment ? this.#kindOf(segment) : null;\n if (!segment || !kind) return;\n // Logical, not physical, for the horizontal pair: the segments form an\n // ordered row laid out inline, so a right-to-left writing direction mirrors\n // them. `ArrowUp` / `ArrowDown` change the *value* and pass through\n // untouched — `logicalArrowKey` only ever rewrites the horizontal pair.\n\n switch (logicalArrowKey(event.key, this.element)) {\n case \"ArrowUp\":\n event.preventDefault();\n this.#step(kind, this.#delta(kind));\n break;\n case \"ArrowDown\":\n event.preventDefault();\n this.#step(kind, -this.#delta(kind));\n break;\n case \"ArrowLeft\":\n event.preventDefault();\n this.#focusSibling(segment, -1);\n break;\n case \"ArrowRight\":\n event.preventDefault();\n this.#focusSibling(segment, 1);\n break;\n case \"Home\":\n event.preventDefault();\n this.#set(kind, this.#bounds(kind).min);\n break;\n case \"End\":\n event.preventDefault();\n this.#set(kind, this.#bounds(kind).max);\n break;\n default:\n if (/^[0-9]$/.test(event.key)) {\n event.preventDefault();\n this.#typeDigit(segment, kind, event.key);\n }\n return;\n }\n // Any non-digit action ends the current direct-entry sequence.\n this.#typeBuffer = \"\";\n this.#typeSegment = null;\n }\n\n /** The per-step delta: minutes step by `step`, others by 1, meridiem toggles. */\n #delta(kind: SegmentKind): number {\n return kind === \"minute\" ? this.stepValue : 1;\n }\n\n /** Steps a segment, wrapping at its bounds and carrying over when enabled. */\n #step(kind: SegmentKind, delta: number): void {\n if (kind === \"meridiem\") {\n this.#set(\"meridiem\", this.#state.meridiem === AM ? PM : AM);\n return;\n }\n const { min, max } = this.#bounds(kind);\n const span = max - min + 1;\n const raw = this.#state[kind] + delta;\n\n if (raw > max || raw < min) {\n if (!this.wrapValue) {\n this.#set(kind, Math.min(max, Math.max(min, raw)));\n return;\n }\n // Wrap within the segment, carrying the overflow into the larger unit.\n const wrapped = ((((raw - min) % span) + span) % span) + min;\n const carry = Math.floor((raw - min) / span);\n this.#set(kind, wrapped);\n this.#carry(kind, carry);\n return;\n }\n this.#set(kind, raw);\n }\n\n /** Propagates a wrap carry from `kind` into the next larger segment. */\n #carry(kind: SegmentKind, amount: number): void {\n if (kind === \"second\") this.#step(\"minute\", amount);\n else if (kind === \"minute\") this.#step(\"hour\", amount);\n // Hours wrap on their own (no day rollover); meridiem has no carry.\n }\n\n /**\n * Sets a segment's value (clamped to its `[min, max]` bounds), re-renders it,\n * and resyncs the composed field. Clamping here guards the direct-entry path:\n * typing `0` into a 12-hour hour (min 1) must not commit an out-of-range\n * `aria-valuenow=\"0\"`. The stepping path already passes in-bounds values, so\n * the clamp is a no-op there.\n */\n #set(kind: SegmentKind, value: number): void {\n const { min, max } = this.#bounds(kind);\n this.#state[kind] = Math.min(max, Math.max(min, value));\n const segment = this.segmentTargets.find((s) => this.#kindOf(s) === kind);\n if (segment) this.#renderSegment(segment);\n this.#syncField(true);\n }\n\n /** Accumulates a typed digit, committing and advancing after two digits. */\n #typeDigit(segment: HTMLElement, kind: SegmentKind, digit: string): void {\n if (kind === \"meridiem\") return;\n if (this.#typeSegment !== kind) this.#typeBuffer = \"\";\n this.#typeSegment = kind;\n\n const { max } = this.#bounds(kind);\n const candidate = Number(`${this.#typeBuffer}${digit}`);\n if (candidate <= max) this.#typeBuffer = `${this.#typeBuffer}${digit}`;\n else this.#typeBuffer = digit; // restart from this digit when it overflows\n\n this.#set(kind, Number(this.#typeBuffer));\n\n // Two digits (or a value that can't grow further) completes the segment.\n if (this.#typeBuffer.length >= 2 || Number(this.#typeBuffer) * 10 > max) {\n this.#typeBuffer = \"\";\n this.#typeSegment = null;\n this.#focusSibling(segment, 1);\n }\n }\n\n /** Moves focus to the previous/next segment, if one exists. */\n #focusSibling(segment: HTMLElement, direction: 1 | -1): void {\n const index = this.segmentTargets.indexOf(segment);\n const next = this.segmentTargets[index + direction];\n next?.focus();\n }\n\n /** Reflects a segment's current value onto its ARIA/text representation. */\n #renderSegment(segment: HTMLElement): void {\n const kind = this.#kindOf(segment);\n if (!kind) return;\n const value = this.#state[kind];\n\n if (kind === \"meridiem\") {\n const text = value === PM ? \"PM\" : \"AM\";\n segment.setAttribute(\"aria-valuenow\", String(value));\n segment.setAttribute(\"aria-valuetext\", text);\n segment.setAttribute(\"aria-valuemin\", String(AM));\n segment.setAttribute(\"aria-valuemax\", String(PM));\n segment.textContent = text;\n return;\n }\n\n const { min, max } = this.#bounds(kind);\n const text = String(value).padStart(2, \"0\");\n segment.setAttribute(\"aria-valuenow\", String(value));\n segment.setAttribute(\"aria-valuetext\", text);\n segment.setAttribute(\"aria-valuemin\", String(min));\n segment.setAttribute(\"aria-valuemax\", String(max));\n segment.textContent = text;\n }\n\n /** Composes `HH:MM[:SS]` and notifies both native-form and widget consumers. */\n #syncField(notify: boolean): void {\n const h24 = this.#hours24();\n const parts = [pad(h24), pad(this.#state.minute)];\n if (this.secondsValue) parts.push(pad(this.#state.second));\n const value = parts.join(\":\");\n\n const fieldChanged = this.hasFieldTarget && this.fieldTarget.value !== value;\n if (fieldChanged) {\n this.fieldTarget.value = value;\n }\n if (notify && fieldChanged) {\n // Match a native form control: only a real committed value change bubbles\n // to form validation, dirty tracking, and auto-submit consumers.\n this.fieldTarget.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n // The widget event describes its composed state and remains available even\n // when no form field target exists.\n if (notify && value !== this.#lastValue) this.dispatch(\"change\", { detail: { value } });\n this.#lastValue = value;\n }\n\n /** Converts the displayed hour (+ meridiem in 12-hour mode) to 24-hour. */\n #hours24(): number {\n if (this.hourCycleValue !== 12) return this.#state.hour;\n const base = this.#state.hour % 12; // 12 → 0\n return base + (this.#state.meridiem === PM ? 12 : 0);\n }\n\n /** The inclusive `[min, max]` bounds for a segment kind. */\n #bounds(kind: SegmentKind): { min: number; max: number } {\n switch (kind) {\n case \"hour\":\n return this.hourCycleValue === 12 ? { min: 1, max: 12 } : { min: 0, max: 23 };\n case \"minute\":\n case \"second\":\n return { min: 0, max: 59 };\n case \"meridiem\":\n return { min: AM, max: PM };\n }\n }\n\n /** Reads a segment's declared kind, or null when absent/invalid. */\n #kindOf(segment: HTMLElement): SegmentKind | null {\n const kind = segment.getAttribute(\"data-segment\");\n if (kind === \"hour\" || kind === \"minute\" || kind === \"second\" || kind === \"meridiem\") {\n return kind;\n }\n return null;\n }\n}\n\n/** Zero-pads a number to two digits. */\nfunction pad(value: number): string {\n return String(value).padStart(2, \"0\");\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/time_picker_controller.ts"],"names":[],"mappings":"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","file":"time_picker_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","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/** A time segment kind, as declared by `data-segment` on each spinbutton. */\ntype SegmentKind = \"hour\" | \"minute\" | \"second\" | \"meridiem\";\n\n/** Canonical controller state. Hours are always stored on the 24-hour clock. */\ninterface TimeState {\n hour: number;\n minute: number;\n second: number;\n}\n\n/** Meridiem encoding exposed through the meridiem spinbutton. */\nconst AM = 0;\nconst PM = 1;\nconst SECONDS_PER_MINUTE = 60;\nconst SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;\nconst SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;\n\n/** Modifier keys that reserve document/browser shortcuts outside the widget. */\nconst hasModifier = (event: KeyboardEvent): boolean =>\n event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;\n\n/**\n * Headless, accessible **time picker** behavior. Each segment (hour, minute,\n * optional second, optional AM/PM) is an APG **Spinbutton**; the controller\n * composes them into an `HH:MM[:SS]` value mirrored to an optional hidden field.\n *\n * Markup contract (identifier: `stimeo--time-picker`):\n * <div data-controller=\"stimeo--time-picker\"\n * data-stimeo--time-picker-hour-cycle-value=\"24\"\n * role=\"group\" aria-label=\"Time\">\n * <span role=\"spinbutton\" aria-label=\"Hours\" tabindex=\"0\"\n * aria-valuenow=\"9\" aria-valuemin=\"0\" aria-valuemax=\"23\" aria-valuetext=\"09\"\n * data-segment=\"hour\" data-stimeo--time-picker-target=\"segment\"\n * data-action=\"keydown->stimeo--time-picker#onKeydown\">09</span>\n * <span aria-hidden=\"true\">:</span>\n * <span role=\"spinbutton\" aria-label=\"Minutes\" tabindex=\"0\"\n * data-segment=\"minute\" data-stimeo--time-picker-target=\"segment\"\n * data-action=\"keydown->stimeo--time-picker#onKeydown\">30</span>\n * <input type=\"hidden\" data-stimeo--time-picker-target=\"field\" />\n * </div>\n *\n * `reconcile` dispatches `{ value: string }`.\n *\n * @remarks\n * `segment` targets declare the spinbuttons; the optional `field` target receives\n * the composed form value. `hourCycle` selects 12- or 24-hour presentation\n * (default 24), `step` is the positive integer minute step (default 1), `seconds`\n * includes seconds in the composed value (default false), and `wrap` controls\n * whether stepping crosses segment bounds (default true). The author decides\n * which optional segment targets are present; `seconds` does not create or hide\n * markup.\n *\n * Every segment is its own Tab stop (multi-tabstop, *not* roving), while\n * `ArrowLeft`/`ArrowRight` provide auxiliary movement. `ArrowUp`/`ArrowDown`\n * step the focused spinbutton; wrapping seconds or minutes carries into the next\n * unit, and 12-hour stepping crosses AM/PM at noon and midnight. Typing digits\n * enters a value directly and advances after completion. The digit buffer is\n * discarded on another action, target replacement, or focus departure.\n *\n * A user action that changes the composed value dispatches exactly one bubbling\n * native `change` from the field, when present, and one\n * `stimeo--time-picker:change` with `{ value: string }`. Retained\n * `aria-valuenow`/`data-segment` changes, runtime targets, and\n * `hourCycle`/`seconds` Value changes are reconciled for Turbo morph\n * compatibility; when such a pass moves the composed value — dropping a `second`\n * segment, say — it reports `stimeo--time-picker:reconcile` with the same detail\n * instead, so a consumer can tell its own edit from this controller's repair.\n * Initial connection reports neither.\n */\nexport class TimePickerController extends Controller<HTMLElement> {\n static override targets = [\"segment\", \"field\"];\n static override values = {\n hourCycle: { type: Number, default: 24 },\n step: { type: Number, default: 1 },\n seconds: { type: Boolean, default: false },\n wrap: { type: Boolean, default: true },\n };\n static actions = [\"onKeydown\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly segmentTargets: HTMLElement[];\n declare readonly fieldTarget: HTMLInputElement;\n declare readonly hasFieldTarget: boolean;\n declare hourCycleValue: number;\n declare stepValue: number;\n declare secondsValue: boolean;\n declare wrapValue: boolean;\n\n /** Collapses target, Value, and retained-attribute morphs into one silent render. */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileDom());\n /** Canonical state; the displayed hour and meridiem are derived from this value. */\n #state: TimeState = { hour: 0, minute: 0, second: 0 };\n /** Direct-entry digit buffer and the segment it belongs to. */\n #typeBuffer = \"\";\n #typeSegment: SegmentKind | null = null;\n /** Last composed value, used to suppress duplicate user notifications. */\n #lastValue = \"\";\n #observer: MutationObserver | null = null;\n #connected = false;\n #domDirty = false;\n\n /** Seeds state from the DOM, renders canonical ARIA, and starts morph observation. */\n override connect(): void {\n this.#connected = true;\n this.#reconcile.activate();\n this.element.addEventListener(\"focusout\", this.#onFocusOut);\n this.#domDirty = false;\n this.#adoptDomState();\n this.#render();\n this.#observeMutations();\n }\n\n /** Releases the observer, listener, queued reconciliation, and transient input state. */\n override disconnect(): void {\n this.#connected = false;\n this.#reconcile.cancel();\n this.element.removeEventListener(\"focusout\", this.#onFocusOut);\n this.#observer?.disconnect();\n this.#observer = null;\n this.#domDirty = false;\n this.#clearTypeBuffer();\n this.#lastValue = \"\";\n }\n\n /** Re-renders the canonical instant when 12/24-hour presentation changes at runtime. */\n hourCycleValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Recomposes the optional seconds portion after a runtime Value change. */\n secondsValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Adopts a segment inserted or replaced by a Turbo morph. */\n segmentTargetConnected(): void {\n this.#domDirty = true;\n this.#clearTypeBuffer();\n this.#reconcile.schedule();\n }\n\n /** Rebuilds state after a segment leaves the retained controller element. */\n segmentTargetDisconnected(): void {\n this.#domDirty = true;\n this.#clearTypeBuffer();\n this.#reconcile.schedule();\n }\n\n /** Reflects the current composed value into a field added or replaced at runtime. */\n fieldTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles silently when the optional field leaves at runtime. */\n fieldTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Handles APG stepping, inter-segment focus moves, jumps, and direct entry. */\n onKeydown(event: KeyboardEvent): void {\n if (isReservedArrowChord(event)) return;\n const segment = (event.target as HTMLElement | null)?.closest<HTMLElement>(\n \"[data-stimeo--time-picker-target~='segment']\",\n );\n if (!segment || !this.segmentTargets.includes(segment)) return;\n const kind = this.#kindOf(segment);\n if (!kind) return;\n\n const key = logicalArrowKey(event.key, this.element);\n if ((key === \"Home\" || key === \"End\") && hasModifier(event)) return;\n\n switch (key) {\n case \"ArrowUp\":\n event.preventDefault();\n this.#step(kind, this.#delta(kind));\n break;\n case \"ArrowDown\":\n event.preventDefault();\n this.#step(kind, -this.#delta(kind));\n break;\n case \"ArrowLeft\":\n event.preventDefault();\n this.#focusSibling(segment, -1);\n break;\n case \"ArrowRight\":\n event.preventDefault();\n this.#focusSibling(segment, 1);\n break;\n case \"Home\":\n event.preventDefault();\n this.#setDisplayed(kind, this.#bounds(kind).min);\n this.#commitRender();\n break;\n case \"End\":\n event.preventDefault();\n this.#setDisplayed(kind, this.#bounds(kind).max);\n this.#commitRender();\n break;\n default:\n if (kind !== \"meridiem\" && /^[0-9]$/.test(event.key) && !hasModifier(event)) {\n event.preventDefault();\n this.#typeDigit(segment, kind, event.key);\n return;\n }\n return;\n }\n this.#clearTypeBuffer();\n }\n\n /** Clears direct-entry state when Tab, Shift+Tab, or pointer focus leaves a segment. */\n readonly #onFocusOut = (event: FocusEvent): void => {\n const segment = (event.target as HTMLElement | null)?.closest<HTMLElement>(\n \"[data-stimeo--time-picker-target~='segment']\",\n );\n if (!segment || !this.segmentTargets.includes(segment)) return;\n this.#clearTypeBuffer();\n };\n\n /** Uses the normalized positive-integer minute step; other segments step by one. */\n #delta(kind: SegmentKind): number {\n if (kind !== \"minute\") return 1;\n const step = Math.trunc(this.stepValue);\n return Number.isFinite(step) && step > 0 ? step : 1;\n }\n\n /** Mutates one complete action and then renders/notifies its final value once. */\n #step(kind: SegmentKind, delta: number): void {\n if (kind === \"meridiem\" && this.wrapValue) {\n this.#state.hour = modulo(this.#state.hour + 12, 24);\n } else if (this.wrapValue) {\n const scale = kind === \"hour\" ? SECONDS_PER_HOUR : kind === \"minute\" ? SECONDS_PER_MINUTE : 1;\n this.#setFromSeconds(this.#secondsOfDay + delta * scale);\n } else if (kind === \"hour\") {\n // The displayed hour is a projection of the canonical one, so stepping it\n // moves the instant: 11 → 12 crosses noon or midnight even here, where the\n // step never leaves the segment's own bounds. Clamping on the canonical\n // clock is what \"no wrapping\" means — the day is not crossed.\n this.#state.hour = Math.min(23, Math.max(0, this.#state.hour + delta));\n } else {\n const { min, max } = this.#bounds(kind);\n const displayed = this.#displayValue(kind);\n this.#setDisplayed(kind, Math.min(max, Math.max(min, displayed + delta)));\n }\n this.#commitRender();\n }\n\n /** Accumulates a typed digit, rendering once and advancing after completion. */\n #typeDigit(segment: HTMLElement, kind: Exclude<SegmentKind, \"meridiem\">, digit: string): void {\n if (this.#typeSegment !== kind) this.#typeBuffer = \"\";\n this.#typeSegment = kind;\n\n const { max } = this.#bounds(kind);\n const candidate = Number(`${this.#typeBuffer}${digit}`);\n this.#typeBuffer = candidate <= max ? `${this.#typeBuffer}${digit}` : digit;\n\n this.#setDisplayed(kind, Number(this.#typeBuffer));\n this.#commitRender();\n\n if (this.#typeBuffer.length >= 2 || Number(this.#typeBuffer) * 10 > max) {\n this.#clearTypeBuffer();\n this.#focusSibling(segment, 1);\n }\n }\n\n /** Moves focus to the previous/next valid segment, if one exists. */\n #focusSibling(segment: HTMLElement, direction: 1 | -1): void {\n const segments = this.segmentTargets.filter((candidate) => this.#kindOf(candidate) !== null);\n const index = segments.indexOf(segment);\n segments[index + direction]?.focus();\n }\n\n /** Clears the direct-entry buffer and its owning segment together. */\n #clearTypeBuffer(): void {\n this.#typeBuffer = \"\";\n this.#typeSegment = null;\n }\n\n /** Reconciles one coalesced target, Value, or retained-attribute mutation batch. */\n #reconcileDom(): void {\n if (this.#domDirty) {\n this.#domDirty = false;\n this.#clearTypeBuffer();\n this.#adoptDomState();\n }\n this.#render();\n }\n\n /** Rebuilds canonical state from the targets; an absent hour represents midnight. */\n #adoptDomState(): void {\n let displayedHour = 0;\n let meridiem = AM;\n let minute = 0;\n let second = 0;\n\n for (const segment of this.segmentTargets) {\n const kind = this.#kindOf(segment);\n if (!kind) continue;\n const value = this.#authoredValue(segment, kind);\n if (kind === \"hour\") displayedHour = value;\n else if (kind === \"minute\") minute = value;\n else if (kind === \"second\") second = value;\n else meridiem = value;\n }\n\n this.#state = {\n hour:\n this.hourCycleValue === 12\n ? (displayedHour % 12) + (meridiem === PM ? 12 : 0)\n : displayedHour,\n minute,\n second,\n };\n }\n\n /** Reads and integer-clamps one authored `aria-valuenow`. */\n #authoredValue(segment: HTMLElement, kind: SegmentKind): number {\n const raw = Number(segment.getAttribute(\"aria-valuenow\"));\n const { min, max } = this.#bounds(kind);\n if (!Number.isFinite(raw)) return min;\n return Math.min(max, Math.max(min, Math.trunc(raw)));\n }\n\n /** Writes a displayed segment value back into canonical state without rendering. */\n #setDisplayed(kind: SegmentKind, raw: number): void {\n const { min, max } = this.#bounds(kind);\n const value = Math.min(max, Math.max(min, Math.trunc(raw)));\n if (kind === \"hour\") {\n this.#state.hour =\n this.hourCycleValue === 12 ? (value % 12) + (this.#state.hour >= 12 ? 12 : 0) : value;\n } else if (kind === \"minute\" || kind === \"second\") {\n this.#state[kind] = value;\n } else {\n this.#state.hour = (this.#state.hour % 12) + (value === PM ? 12 : 0);\n }\n }\n\n /** Converts seconds with day wrapping into the canonical three-unit state. */\n #setFromSeconds(raw: number): void {\n const seconds = modulo(raw, SECONDS_PER_DAY);\n this.#state.hour = Math.floor(seconds / SECONDS_PER_HOUR);\n this.#state.minute = Math.floor((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE);\n this.#state.second = seconds % SECONDS_PER_MINUTE;\n }\n\n /** Reflects canonical state to every segment without dispatching user events. */\n #renderSegments(): void {\n this.#withoutObservation(() => {\n for (const segment of this.segmentTargets) this.#renderSegment(segment);\n });\n }\n\n /**\n * Reflects canonical state during connection or DOM reconciliation.\n *\n * No user edit reaches here, so `change` never fires from this path. A pass\n * that moves the committed value reports `reconcile` instead.\n *\n * @stimeoRenderRoot\n */\n #render(): void {\n this.#renderSegments();\n const value = this.#composedValue;\n const previous = this.#lastValue;\n this.#writeField(value);\n this.#lastValue = value;\n // A reconciliation can move the committed value — dropping a `second`\n // segment recomposes without it, and a `seconds` Value change adds or\n // removes the unit. That is this controller's decision rather than the\n // user's, so it is reported apart from `change`. The empty baseline is the\n // initial connection, which reports nothing.\n if (previous !== \"\" && value !== previous) {\n this.dispatch(\"reconcile\", { detail: { value } });\n }\n }\n\n /** Reflects one completed user action and dispatches its final change exactly once. */\n #commitRender(): void {\n this.#renderSegments();\n const value = this.#composedValue;\n const fieldChanged = this.#writeField(value);\n if (fieldChanged) this.fieldTarget.dispatchEvent(new Event(\"change\", { bubbles: true }));\n if (value !== this.#lastValue) this.dispatch(\"change\", { detail: { value } });\n this.#lastValue = value;\n }\n\n /** Reflects one segment's value and controller-owned ARIA bounds/text. */\n #renderSegment(segment: HTMLElement): void {\n const kind = this.#kindOf(segment);\n if (!kind) return;\n const value = this.#displayValue(kind);\n const text = kind === \"meridiem\" ? (value === PM ? \"PM\" : \"AM\") : pad(value);\n const { min, max } = this.#bounds(kind);\n\n segment.setAttribute(\"aria-valuenow\", String(value));\n segment.setAttribute(\"aria-valuetext\", text);\n segment.setAttribute(\"aria-valuemin\", String(min));\n segment.setAttribute(\"aria-valuemax\", String(max));\n segment.textContent = text;\n }\n\n /** Writes a composed value to the optional form field and reports whether it changed. */\n #writeField(value: string): boolean {\n if (!this.hasFieldTarget || this.fieldTarget.value === value) return false;\n this.fieldTarget.value = value;\n return true;\n }\n\n /** The canonical form value composed as `HH:MM[:SS]`. */\n get #composedValue(): string {\n const parts = [pad(this.#state.hour), pad(this.#state.minute)];\n if (this.secondsValue) parts.push(pad(this.#state.second));\n return parts.join(\":\");\n }\n\n /** Returns a canonical unit in the presentation form exposed by its segment. */\n #displayValue(kind: SegmentKind): number {\n if (kind === \"hour\") {\n return this.hourCycleValue === 12 ? this.#state.hour % 12 || 12 : this.#state.hour;\n }\n if (kind === \"meridiem\") return this.#state.hour >= 12 ? PM : AM;\n return this.#state[kind];\n }\n\n /** The inclusive controller-owned bounds for a segment kind. */\n #bounds(kind: SegmentKind): { min: number; max: number } {\n switch (kind) {\n case \"hour\":\n return this.hourCycleValue === 12 ? { min: 1, max: 12 } : { min: 0, max: 23 };\n case \"minute\":\n case \"second\":\n return { min: 0, max: 59 };\n case \"meridiem\":\n return { min: AM, max: PM };\n }\n }\n\n /** Watches retained segment state while excluding this controller's own reflections. */\n #observeMutations(): void {\n const observer = new MutationObserver((records) => {\n const changed = records.some(\n ({ target }) => target instanceof HTMLElement && this.segmentTargets.includes(target),\n );\n if (!changed) return;\n this.#domDirty = true;\n this.#clearTypeBuffer();\n this.#reconcile.schedule();\n });\n this.#observer = observer;\n this.#observeWith(observer);\n }\n\n /** Registers retained attribute observation on the controller subtree. */\n #observeWith(observer: MutationObserver): void {\n observer.observe(this.element, {\n subtree: true,\n attributes: true,\n attributeFilter: [\"aria-valuenow\", \"data-segment\"],\n });\n }\n\n /** Temporarily pauses observation so derived ARIA writes cannot become DOM input. */\n #withoutObservation(run: () => void): void {\n const observer = this.#observer;\n observer?.disconnect();\n try {\n run();\n } finally {\n if (observer && this.#connected) this.#observeWith(observer);\n }\n }\n\n /** Reads a segment's declared kind, or null when absent/invalid. */\n #kindOf(segment: HTMLElement): SegmentKind | null {\n const kind = segment.getAttribute(\"data-segment\");\n if (kind === \"hour\" || kind === \"minute\" || kind === \"second\" || kind === \"meridiem\") {\n return kind;\n }\n return null;\n }\n\n /** Canonical state as seconds since the start of its nominal day. */\n get #secondsOfDay(): number {\n return (\n this.#state.hour * SECONDS_PER_HOUR +\n this.#state.minute * SECONDS_PER_MINUTE +\n this.#state.second\n );\n }\n}\n\n/** Positive modulo for wrapping values in both keyboard directions. */\nfunction modulo(value: number, modulus: number): number {\n return ((value % modulus) + modulus) % modulus;\n}\n\n/** Zero-pads a number to two digits. */\nfunction pad(value: number): string {\n return String(value).padStart(2, \"0\");\n}\n"]}
|
|
@@ -27,6 +27,10 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
27
27
|
* Implements WAI-ARIA live region status/alert announcements, limits simultaneous
|
|
28
28
|
* elements, and pauses dismiss timeouts on hover or focus to comply with WCAG 2.2.1.
|
|
29
29
|
*
|
|
30
|
+
* `dismiss` dispatches `{ item: HTMLElement, reason: "timeout" | "user" }`.
|
|
31
|
+
*
|
|
32
|
+
* `show` dispatches `{ item: HTMLElement }`.
|
|
33
|
+
*
|
|
30
34
|
* @remarks
|
|
31
35
|
* Behavior only. The controller handles state updates via `data-state` and lifecycle events
|
|
32
36
|
* while leaving visual styling completely to the client's CSS transitions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/safe_timeout.ts","../../src/utils/transition_completion.ts","../../src/controllers/toast_controller.ts"],"names":[],"mappings":"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","file":"toast_controller.js","sourcesContent":["/**\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 { SafeTimeout } from \"./safe_timeout\";\n\n/** Computed transition fields needed to determine when an element has settled. */\nexport type TransitionStyle = Pick<\n CSSStyleDeclaration,\n \"transitionProperty\" | \"transitionDuration\" | \"transitionDelay\"\n>;\n\n/** Parsed transition property paired with its effective duration and delay. */\ninterface TransitionTiming {\n property: string;\n totalMs: number;\n}\n\n/** Parses a CSS `<time>` into milliseconds, returning `0` for malformed values. */\nfunction timeMs(value: string): number {\n const trimmed = value.trim();\n const amount = Number.parseFloat(trimmed);\n if (!Number.isFinite(amount)) return 0;\n if (trimmed.endsWith(\"ms\")) return amount;\n if (trimmed.endsWith(\"s\")) return amount * 1000;\n return 0;\n}\n\n/** Splits a computed comma-separated CSS list into normalized, non-empty items. */\nfunction cssList(value: string): string[] {\n return value\n .split(\",\")\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\n/**\n * Pairs transition properties, durations, and delays using CSS list repetition.\n *\n * The `transition-property` list determines the number of transitions. Shorter\n * duration/delay lists repeat, while extra values are ignored. A negative delay\n * shortens the remaining wall-clock time, and a non-positive total is settled.\n */\nfunction transitionTimings(style: TransitionStyle): TransitionTiming[] {\n const properties = cssList(style.transitionProperty);\n const durations = cssList(style.transitionDuration).map(timeMs);\n const delays = cssList(style.transitionDelay).map(timeMs);\n const effectiveProperties =\n properties.length > 0\n ? properties\n : Array.from({ length: Math.max(durations.length, delays.length, 1) }, () => \"all\");\n const effectiveDurations = durations.length > 0 ? durations : [0];\n const effectiveDelays = delays.length > 0 ? delays : [0];\n\n return effectiveProperties\n .filter((property) => property !== \"none\")\n .map((property, index) => ({\n property,\n totalMs: Math.max(\n 0,\n (effectiveDurations[index % effectiveDurations.length] ?? 0) +\n (effectiveDelays[index % effectiveDelays.length] ?? 0),\n ),\n }));\n}\n\n/** Maximum total over already-parsed timings (0 when the list is empty). */\nfunction maxTotalMs(timings: TransitionTiming[]): number {\n return timings.reduce((max, { totalMs }) => Math.max(max, totalMs), 0);\n}\n\n/** Maximum effective transition duration plus delay, in milliseconds. */\nexport function maxTransitionTotalMs(style: TransitionStyle): number {\n return maxTotalMs(transitionTimings(style));\n}\n\n/** Optional tuning for a single {@link TransitionCompletion.wait} call. */\nexport interface TransitionWaitOptions {\n /**\n * Positive wall-clock override (ms) for the bounded fallback, replacing the\n * auto-computed `max + 50ms` timer verbatim. It also keeps the wait armed for\n * a computed 0ms transition (no synchronous completion): the consumer asserts\n * a transition budget that computed styles cannot see. Non-positive and\n * non-finite (`NaN` / `Infinity`) values are ignored.\n */\n timeoutMs?: number;\n}\n\n/**\n * Owns one cancellable wait for an element's CSS transitions to settle.\n *\n * When available, active CSS transitions provide their expanded property names.\n * Tracking those names lets shorthand declarations settle from their longhand\n * terminal events. A terminal event from an interrupted phase is ignored while a\n * replacement transition for the same property is still active. Because that set\n * is sampled when the wait is armed, a property whose transition only starts a\n * frame later would otherwise be settled over; the still-running set is therefore\n * re-read before completing and adopted when it is not yet empty. Without Web\n * Animations data, explicit transition properties retain the declared-property\n * behavior and the ambiguous `transition-property: all` form waits until the\n * computed maximum wall-clock time. A `max + 50ms` timeout is always armed\n * because browsers emit no terminal event when a property never transitions, an\n * ancestor disappears, or rendering is otherwise interrupted; a consumer with\n * better knowledge can replace that fallback via\n * {@link TransitionWaitOptions.timeoutMs}.\n */\nexport class TransitionCompletion {\n readonly #timers = new SafeTimeout();\n #element: HTMLElement | null = null;\n #complete: (() => void) | null = null;\n #pendingProperties: Set<string> | null = null;\n #deadline = 0;\n\n /**\n * Replaces any prior wait and invokes `complete` synchronously for a 0ms\n * transition (including when `getComputedStyle` is unavailable).\n *\n * With a positive `options.timeoutMs` the synchronous fast-path is skipped and\n * the override replaces the auto-computed fallback (see {@link TransitionWaitOptions}).\n */\n wait(element: HTMLElement, complete: () => void, options: TransitionWaitOptions = {}): void {\n this.cancel();\n\n const requested = options.timeoutMs ?? 0;\n const override = Number.isFinite(requested) && requested > 0 ? requested : 0;\n // A non-browser / partial-DOM environment cannot report a computable\n // transition, which is indistinguishable from a 0ms one.\n const timings =\n typeof window.getComputedStyle === \"function\"\n ? transitionTimings(window.getComputedStyle(element))\n : [];\n const maximum = maxTotalMs(timings);\n if (maximum <= 0 && override <= 0) {\n complete();\n return;\n }\n\n this.#element = element;\n this.#complete = complete;\n this.#deadline = Date.now() + maximum;\n const activeProperties = this.#activeTransitionProperties(element);\n this.#pendingProperties =\n activeProperties.size > 0 ? activeProperties : this.#explicitPendingProperties(timings);\n element.addEventListener(\"transitionend\", this.#onTerminal);\n element.addEventListener(\"transitioncancel\", this.#onTerminal);\n this.#timers.set(() => this.#finish(), override > 0 ? override : maximum + 50);\n }\n\n /** Cancels the pending wait without invoking its completion callback. */\n cancel(): void {\n this.#complete = null;\n this.#teardown();\n }\n\n /**\n * Handles terminal events from the observed element only.\n *\n * For explicit property lists, every declared positive-time property must\n * settle. For `all`, no reliable property set exists, so an event can finish\n * only after the computed maximum time; the safety timeout owns the usual path.\n */\n readonly #onTerminal = (event: Event): void => {\n if (event.target !== this.#element) return;\n const transitionEvent = event as TransitionEvent;\n // A pseudo-element's transition is reported with its originating element\n // as `target`; it must not settle the element's own transition.\n if (transitionEvent.pseudoElement) return;\n\n if (this.#pendingProperties) {\n const propertyName = transitionEvent.propertyName;\n const active = this.#activeTransitionProperties(this.#element);\n // An interrupted transition queues its cancel event before the replacement\n // phase starts, but dispatch can happen after the new wait is armed. The\n // replacement is then the active transition for this property, so the stale\n // terminal event must not settle the new phase.\n if (active.has(propertyName)) return;\n if (!this.#pendingProperties.delete(propertyName)) return;\n if (this.#pendingProperties.size > 0) return;\n // The tracked set is an arm-time snapshot, so a transition the phase starts\n // one frame later is absent from it. Re-reading the still-running set before\n // settling adopts those stragglers instead of completing over them; the\n // fallback timer stays armed, so this cannot extend the wait unboundedly.\n if (active.size > 0) {\n this.#pendingProperties = active;\n return;\n }\n this.#finish();\n return;\n }\n\n if (Date.now() >= this.#deadline) this.#finish();\n };\n\n /**\n * Returns active CSS transition properties expanded to the names reported by\n * terminal events. CSS animations and pseudo-element effects are excluded.\n */\n #activeTransitionProperties(element: HTMLElement | null): Set<string> {\n if (!element || typeof element.getAnimations !== \"function\") return new Set();\n\n try {\n const properties = element.getAnimations().flatMap((animation) => {\n if (animation.playState === \"idle\" || animation.playState === \"finished\") return [];\n const effect = animation.effect as KeyframeEffect | null;\n // A pseudo-element effect names the originating element as its target, so\n // `target` alone cannot separate it. Its terminal events are ignored, so\n // tracking its properties would only strand the wait on the fallback.\n if (effect?.pseudoElement) return [];\n const target = effect?.target;\n if (target && target !== element) return [];\n const property = (animation as Partial<CSSTransition>).transitionProperty;\n return typeof property === \"string\" && property.length > 0 ? [property] : [];\n });\n return new Set(properties);\n } catch {\n // Partial DOM implementations can expose getAnimations without supporting\n // it. The declared-property tracker and bounded timer remain safe fallbacks.\n return new Set();\n }\n }\n\n /** Returns explicit positive-time properties, or `null` for the ambiguous `all`. */\n #explicitPendingProperties(timings: TransitionTiming[]): Set<string> | null {\n if (timings.some(({ property }) => property === \"all\")) return null;\n const pending = new Set(\n timings.filter(({ totalMs }) => totalMs > 0).map(({ property }) => property),\n );\n // An empty set is only reachable through a timeout override (a 0ms transition\n // otherwise completes synchronously). With nothing to track, fall back to the\n // deadline rule so any terminal event can settle the wait, exactly like `all`.\n return pending.size > 0 ? pending : null;\n }\n\n /** Completes exactly once, releasing listeners and the fallback before the callback. */\n #finish(): void {\n const complete = this.#complete;\n if (!complete) return;\n this.#complete = null;\n this.#teardown();\n complete();\n }\n\n /** Releases the exact element listeners and timer owned by the current wait. */\n #teardown(): void {\n this.#timers.clearAll();\n this.#element?.removeEventListener(\"transitionend\", this.#onTerminal);\n this.#element?.removeEventListener(\"transitioncancel\", this.#onTerminal);\n this.#element = null;\n this.#pendingProperties = null;\n this.#deadline = 0;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport { maxTransitionTotalMs } from \"../utils/transition_completion\";\n\nconst DELEGATED_EVENTS = [\"click\", \"focusin\", \"focusout\", \"keydown\", \"mouseover\", \"mouseout\"];\n\n/**\n * Headless, highly accessible toast notification behavior.\n *\n * Markup contract (identifier: `stimeo--toast`):\n * <div data-controller=\"stimeo--toast\"\n * data-stimeo--toast-duration-value=\"5000\"\n * data-stimeo--toast-max-value=\"3\">\n * <!-- Attribute-only trigger: no hand-written JS required. -->\n * <button data-action=\"click->stimeo--toast#show\"\n * data-stimeo--toast-body-param=\"Saved\"\n * data-stimeo--toast-type-param=\"status\">Show</button>\n * <!-- The live region is a descendant; the controller element only needs\n * to enclose both the trigger(s) and the list/template targets. -->\n * <div role=\"region\" aria-label=\"Notifications\">\n * <ol data-stimeo--toast-target=\"list\"></ol>\n * <template data-stimeo--toast-target=\"template\">\n * <li data-stimeo--toast-target=\"item\" tabindex=\"0\">\n * <span role=\"status\" data-toast-slot=\"body\"></span>\n * <button type=\"button\" data-toast-dismiss>Dismiss</button>\n * </li>\n * </template>\n * </div>\n * </div>\n *\n * Implements WAI-ARIA live region status/alert announcements, limits simultaneous\n * elements, and pauses dismiss timeouts on hover or focus to comply with WCAG 2.2.1.\n *\n * @remarks\n * Behavior only. The controller handles state updates via `data-state` and lifecycle events\n * while leaving visual styling completely to the client's CSS transitions.\n */\nexport class ToastController extends Controller<HTMLElement> {\n static override targets = [\"list\", \"template\", \"item\"];\n static override values = {\n duration: { type: Number, default: 0 },\n max: { type: Number, default: 3 },\n };\n static actions = [\"dismiss\", \"onKeydown\", \"pause\", \"resume\", \"show\"] as const;\n static events = [\"dismiss\", \"show\"] as const;\n\n declare readonly listTarget: HTMLElement;\n declare readonly templateTarget: HTMLTemplateElement;\n declare readonly itemTargets: HTMLElement[];\n declare readonly hasListTarget: boolean;\n declare readonly hasTemplateTarget: boolean;\n\n declare durationValue: number;\n declare maxValue: number;\n\n /**\n * Registry for every auto-dismiss and transition-finalize timer the controller\n * schedules. {@link SafeTimeout} owns *registration and teardown only*; the\n * pause/resume remaining-time accounting stays in `#activeTimeouts` so the\n * per-widget WCAG 2.2.1 semantics are not flattened into the helper.\n */\n #timers = new SafeTimeout();\n\n /**\n * Pending one-shot `requestAnimationFrame` handles (the entering→visible flip).\n * Tracked so {@link disconnect} can cancel any that have not fired, preventing a\n * detached element from being mutated after it leaves the DOM (Turbo).\n */\n #rafHandles = new Map<HTMLElement, number>();\n\n /** Track active timeouts mapped by each toast element for safe cancellation. */\n #activeTimeouts = new Map<HTMLElement, { id: number; startedAt: number; remaining: number }>();\n\n /** Track active pause reasons (hover/focus) per toast for WCAG 2.2.1 pause/resume. */\n #pauseReasons = new Map<HTMLElement, Set<string>>();\n\n /** The stable list that owns delegated listeners for dynamically added items. */\n #delegatedList: HTMLElement | null = null;\n\n override connect(): void {\n this.#connectDelegatedEvents();\n this.enforceMaxLimit();\n for (const item of this.itemTargets) {\n if (!this.#activeTimeouts.has(item) && item.dataset.state !== \"leaving\") {\n this.#startTimer(item);\n }\n }\n }\n\n override disconnect(): void {\n // SafeTimeout owns every auto-dismiss + finalize timer; one call tears them\n // all down so none fires against the detached controller.\n this.#disconnectDelegatedEvents();\n this.#timers.clearAll();\n for (const handle of this.#rafHandles.values()) {\n window.cancelAnimationFrame(handle);\n }\n this.#rafHandles.clear();\n this.#activeTimeouts.clear();\n this.#pauseReasons.clear();\n }\n\n /** Rebinds delegated interaction when Turbo replaces the list target in place. */\n listTargetConnected(element: HTMLElement): void {\n if (this.#delegatedList !== element) this.#connectDelegatedEvents(element);\n }\n\n /** Releases delegation only when the removed target is its current owner. */\n listTargetDisconnected(element: HTMLElement): void {\n if (this.#delegatedList === element) this.#disconnectDelegatedEvents();\n }\n\n durationValueChanged(): void {\n for (const item of this.itemTargets) {\n if (item.dataset.state === \"leaving\") continue;\n\n const pauseReasons = this.#pauseReasons.get(item);\n this.#clearTimer(item);\n if (this.durationValue <= 0) {\n item.removeAttribute(\"data-paused\");\n } else if (pauseReasons && pauseReasons.size > 0) {\n this.#pauseReasons.set(item, pauseReasons);\n this.#activeTimeouts.set(item, {\n id: 0,\n startedAt: 0,\n remaining: this.durationValue,\n });\n item.setAttribute(\"data-paused\", \"true\");\n } else {\n this.#startTimer(item);\n }\n }\n }\n\n maxValueChanged(): void {\n this.enforceMaxLimit();\n }\n\n /**\n * Stimulus lifecycle callback triggered automatically when a new item target\n * enters the DOM. Perfectly handles dynamic client-side injections and server-side\n * Turbo Stream appends alike.\n */\n itemTargetConnected(element: HTMLElement): void {\n this.enforceMaxLimit();\n if (element.dataset.state === \"leaving\" || element.parentNode !== this.listTarget) return;\n\n this.#startTimer(element);\n element.setAttribute(\"data-state\", \"entering\");\n this.#cancelAnimation(element);\n const handle = window.requestAnimationFrame(() => {\n this.#rafHandles.delete(element);\n if (element.parentNode !== this.listTarget || element.dataset.state === \"leaving\") return;\n element.setAttribute(\"data-state\", \"visible\");\n });\n this.#rafHandles.set(element, handle);\n }\n\n /** Clears any active timer when a toast is removed from the DOM. */\n itemTargetDisconnected(element: HTMLElement): void {\n this.#clearTimer(element);\n this.#cancelAnimation(element);\n }\n\n /**\n * Shows a new toast. Accepts its content from either a Stimulus action param\n * (attribute-only trigger) or a programmatic `show` CustomEvent `detail`\n * (remote / Turbo trigger); the action param wins when both are present.\n *\n * <button data-action=\"click->stimeo--toast#show\"\n * data-stimeo--toast-body-param=\"Saved\"\n * data-stimeo--toast-type-param=\"status\">Show</button>\n *\n * element.dispatchEvent(new CustomEvent(\"show\", { detail: { body, type } }))\n *\n * Clones the template slot, interpolates the body text, and appends to the list.\n */\n show(event: Event): void {\n if (!this.hasTemplateTarget || !this.hasListTarget) return;\n\n const body = this.#readField(event, \"body\");\n if (!body) return;\n\n const clone = this.templateTarget.content.cloneNode(true) as DocumentFragment;\n const item = clone.querySelector(\"[data-stimeo--toast-target='item']\") as HTMLElement | null;\n if (!item) return;\n\n const bodySlot = item.querySelector<HTMLElement>(\"[data-toast-slot='body']\");\n if (!bodySlot) return;\n bodySlot.textContent = body;\n\n // Validate the live-region role at runtime; untrusted params/detail could\n // otherwise write an invalid ARIA role. Anything but \"alert\" stays polite.\n bodySlot.setAttribute(\"role\", this.#readField(event, \"type\") === \"alert\" ? \"alert\" : \"status\");\n\n this.listTarget.appendChild(item);\n this.dispatch(\"show\", { detail: { item } });\n }\n\n /**\n * Reads a string field from a Stimulus action param or a CustomEvent `detail`,\n * preferring the action param. Returns null unless a non-empty string is found,\n * so untrusted runtime payloads cannot inject non-string values.\n */\n #readField(event: Event, key: \"body\" | \"type\"): string | null {\n const params = (event as { params?: Record<string, unknown> }).params;\n const fromParams = params?.[key];\n if (typeof fromParams === \"string\" && fromParams.length > 0) return fromParams;\n\n const detail = (event as CustomEvent<unknown>).detail;\n if (detail && typeof detail === \"object\" && key in detail) {\n const value = (detail as Record<string, unknown>)[key];\n if (typeof value === \"string\" && value.length > 0) return value;\n }\n return null;\n }\n\n /** Dismisses the toast that contained the trigger. */\n dismiss(event: Event): void {\n const item = this.#itemFromEvent(event);\n if (!item) return;\n\n this.#removeWithTransition(item, \"user\");\n }\n\n /** Dismisses the focused toast when Escape is pressed. */\n onKeydown(event: KeyboardEvent): void {\n if (event.key === \"Escape\") {\n // Leave a press an inner handler already owned.\n // A press during IME composition (a text field inside the toast) cancels\n // the conversion, never the toast.\n if (event.defaultPrevented || event.isComposing) return;\n const item = this.#itemFromEvent(event);\n if (!item) return;\n\n event.preventDefault();\n this.#removeWithTransition(item, \"user\");\n }\n }\n\n /**\n * Pauses the auto-dismiss timer on mouse entry or keyboard focus.\n *\n * Hover and focus are tracked as independent reasons: the timer is only\n * snapshotted on the first active reason, and only resumed once *every*\n * reason has been released (see {@link resume}). This keeps a toast paused\n * while it is still hovered *or* focused, per WCAG 2.2.1.\n */\n pause(event: Event): void {\n const item = this.#itemFromEvent(event);\n if (!item || this.durationValue <= 0) return;\n\n const timeout = this.#activeTimeouts.get(item);\n if (!timeout) return;\n\n const reasons = this.#pauseReasonsFor(item);\n const wasActive = reasons.size > 0;\n reasons.add(this.#pauseReason(event));\n\n // Only snapshot the remaining time on the first reason; subsequent reasons\n // must not recompute elapsed against the already-cleared timer.\n if (wasActive || timeout.id === 0) return;\n\n this.#timers.clear(timeout.id);\n const elapsed = Date.now() - timeout.startedAt;\n const remaining = Math.max(0, timeout.remaining - elapsed);\n\n if (remaining <= 0) {\n this.#removeWithTransition(item, \"timeout\");\n return;\n }\n\n this.#activeTimeouts.set(item, { id: 0, startedAt: 0, remaining });\n item.setAttribute(\"data-paused\", \"true\");\n }\n\n /** Resumes the auto-dismiss timer once both hover and focus have been released. */\n resume(event: Event): void {\n const item = this.#itemFromEvent(event);\n if (!item || this.durationValue <= 0) return;\n\n const reasons = this.#pauseReasonsFor(item);\n reasons.delete(this.#pauseReason(event));\n // Still paused by the other reason (e.g. mouse left but focus remains).\n if (reasons.size > 0) return;\n\n const timeout = this.#activeTimeouts.get(item);\n if (!timeout) return;\n if (timeout.id !== 0 || timeout.remaining <= 0) return;\n\n item.removeAttribute(\"data-paused\");\n this.#startTimer(item, timeout.remaining);\n }\n\n /** Resolves the toast item element a pause/resume event targets. */\n #itemFromEvent(event: Event): HTMLElement | null {\n const target = event.target instanceof Element ? event.target : event.currentTarget;\n if (!(target instanceof Element) || !this.hasListTarget) return null;\n const item = target.closest<HTMLElement>(\"[data-stimeo--toast-target='item']\");\n return item && this.listTarget.contains(item) ? item : null;\n }\n\n /** Classifies a pause/resume event as a hover or focus reason. */\n #pauseReason(event: Event): \"focus\" | \"hover\" {\n return event.type === \"focusin\" || event.type === \"focusout\" ? \"focus\" : \"hover\";\n }\n\n /** Lazily creates and returns the active pause-reason set for an item. */\n #pauseReasonsFor(item: HTMLElement): Set<string> {\n let reasons = this.#pauseReasons.get(item);\n if (!reasons) {\n reasons = new Set<string>();\n this.#pauseReasons.set(item, reasons);\n }\n return reasons;\n }\n\n #startTimer(element: HTMLElement, duration = this.durationValue): void {\n if (\n duration <= 0 ||\n element.dataset.state === \"leaving\" ||\n element.parentNode !== this.listTarget\n ) {\n return;\n }\n\n this.#clearTimer(element);\n const id = this.#timers.set(() => {\n this.#removeWithTransition(element, \"timeout\");\n }, duration);\n\n this.#activeTimeouts.set(element, { id, startedAt: Date.now(), remaining: duration });\n }\n\n #clearTimer(element: HTMLElement): void {\n const timeout = this.#activeTimeouts.get(element);\n if (timeout) {\n if (timeout.id) this.#timers.clear(timeout.id);\n this.#activeTimeouts.delete(element);\n }\n this.#pauseReasons.delete(element);\n }\n\n #removeWithTransition(element: HTMLElement, reason: \"timeout\" | \"user\"): void {\n if (element.dataset.state === \"leaving\" || element.parentNode !== this.listTarget) return;\n\n this.#clearTimer(element);\n this.#cancelAnimation(element);\n element.setAttribute(\"data-state\", \"leaving\");\n\n const finalize = () => {\n if (element.parentNode !== this.listTarget) return;\n this.listTarget.removeChild(element);\n this.dispatch(\"dismiss\", { detail: { item: element, reason } });\n };\n\n const duration = maxTransitionTotalMs(window.getComputedStyle(element));\n if (duration > 0) {\n this.#timers.set(finalize, duration);\n } else {\n finalize();\n }\n }\n\n /**\n * Removes the oldest toasts when the list exceeds `maxValue`.\n *\n * Public (not `#private`) as a deterministic seam: enforcement normally runs\n * from `itemTargetConnected`, a Stimulus callback delivered through a\n * MutationObserver, which a DOM-only environment does not reliably fire — so\n * it can also be invoked directly. It is not a user-wired action.\n */\n enforceMaxLimit(): void {\n const currentItems = this.itemTargets;\n const max = Math.max(0, this.maxValue);\n if (currentItems.length > max) {\n const excessCount = currentItems.length - max;\n for (let i = 0; i < excessCount; i++) {\n const oldest = currentItems[i];\n if (oldest) this.#removeWithTransition(oldest, \"timeout\");\n }\n }\n }\n\n /** Wires stable-container delegation so newly appended items work immediately. */\n #connectDelegatedEvents(list = this.hasListTarget ? this.listTarget : null): void {\n this.#disconnectDelegatedEvents();\n if (!list) return;\n\n this.#delegatedList = list;\n for (const type of DELEGATED_EVENTS) {\n this.#delegatedList.addEventListener(type, this.#onListEvent);\n }\n }\n\n /** Releases every delegated listener from the exact list that owns it. */\n #disconnectDelegatedEvents(): void {\n if (!this.#delegatedList) return;\n\n for (const type of DELEGATED_EVENTS) {\n this.#delegatedList.removeEventListener(type, this.#onListEvent);\n }\n this.#delegatedList = null;\n }\n\n /** Routes every delegated item interaction from the stable list. */\n readonly #onListEvent = (event: Event): void => {\n if (event.type === \"click\") {\n const target = event.target instanceof Element ? event.target : null;\n const trigger = target?.closest<HTMLElement>(\"[data-toast-dismiss]\");\n if (trigger && this.#delegatedList?.contains(trigger)) this.dismiss(event);\n } else if (event.type === \"keydown\") {\n this.onKeydown(event as KeyboardEvent);\n } else if (this.#crossesItemBoundary(event)) {\n if (event.type === \"focusin\" || event.type === \"mouseover\") this.pause(event);\n else this.resume(event);\n }\n };\n\n /** Whether a bubbling focus/pointer event enters or leaves a toast boundary. */\n #crossesItemBoundary(event: Event): boolean {\n const item = this.#itemFromEvent(event);\n if (!item) return false;\n const related = \"relatedTarget\" in event ? event.relatedTarget : null;\n return !(related instanceof Node && item.contains(related));\n }\n\n /** Cancels the pending entering-to-visible frame owned by one item. */\n #cancelAnimation(element: HTMLElement): void {\n const handle = this.#rafHandles.get(element);\n if (handle === undefined) return;\n window.cancelAnimationFrame(handle);\n this.#rafHandles.delete(element);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/safe_timeout.ts","../../src/utils/transition_completion.ts","../../src/controllers/toast_controller.ts"],"names":[],"mappings":"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","file":"toast_controller.js","sourcesContent":["/**\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 { SafeTimeout } from \"./safe_timeout\";\n\n/** Computed transition fields needed to determine when an element has settled. */\nexport type TransitionStyle = Pick<\n CSSStyleDeclaration,\n \"transitionProperty\" | \"transitionDuration\" | \"transitionDelay\"\n>;\n\n/** Parsed transition property paired with its effective duration and delay. */\ninterface TransitionTiming {\n property: string;\n totalMs: number;\n}\n\n/** Parses a CSS `<time>` into milliseconds, returning `0` for malformed values. */\nfunction timeMs(value: string): number {\n const trimmed = value.trim();\n const amount = Number.parseFloat(trimmed);\n if (!Number.isFinite(amount)) return 0;\n if (trimmed.endsWith(\"ms\")) return amount;\n if (trimmed.endsWith(\"s\")) return amount * 1000;\n return 0;\n}\n\n/** Splits a computed comma-separated CSS list into normalized, non-empty items. */\nfunction cssList(value: string): string[] {\n return value\n .split(\",\")\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\n/**\n * Pairs transition properties, durations, and delays using CSS list repetition.\n *\n * The `transition-property` list determines the number of transitions. Shorter\n * duration/delay lists repeat, while extra values are ignored. A negative delay\n * shortens the remaining wall-clock time, and a non-positive total is settled.\n */\nfunction transitionTimings(style: TransitionStyle): TransitionTiming[] {\n const properties = cssList(style.transitionProperty);\n const durations = cssList(style.transitionDuration).map(timeMs);\n const delays = cssList(style.transitionDelay).map(timeMs);\n const effectiveProperties =\n properties.length > 0\n ? properties\n : Array.from({ length: Math.max(durations.length, delays.length, 1) }, () => \"all\");\n const effectiveDurations = durations.length > 0 ? durations : [0];\n const effectiveDelays = delays.length > 0 ? delays : [0];\n\n return effectiveProperties\n .filter((property) => property !== \"none\")\n .map((property, index) => ({\n property,\n totalMs: Math.max(\n 0,\n (effectiveDurations[index % effectiveDurations.length] ?? 0) +\n (effectiveDelays[index % effectiveDelays.length] ?? 0),\n ),\n }));\n}\n\n/** Maximum total over already-parsed timings (0 when the list is empty). */\nfunction maxTotalMs(timings: TransitionTiming[]): number {\n return timings.reduce((max, { totalMs }) => Math.max(max, totalMs), 0);\n}\n\n/** Maximum effective transition duration plus delay, in milliseconds. */\nexport function maxTransitionTotalMs(style: TransitionStyle): number {\n return maxTotalMs(transitionTimings(style));\n}\n\n/** Optional tuning for a single {@link TransitionCompletion.wait} call. */\nexport interface TransitionWaitOptions {\n /**\n * Positive wall-clock override (ms) for the bounded fallback, replacing the\n * auto-computed `max + 50ms` timer verbatim. It also keeps the wait armed for\n * a computed 0ms transition (no synchronous completion): the consumer asserts\n * a transition budget that computed styles cannot see. Non-positive and\n * non-finite (`NaN` / `Infinity`) values are ignored.\n */\n timeoutMs?: number;\n}\n\n/**\n * Owns one cancellable wait for an element's CSS transitions to settle.\n *\n * When available, active CSS transitions provide their expanded property names.\n * Tracking those names lets shorthand declarations settle from their longhand\n * terminal events. A terminal event from an interrupted phase is ignored while a\n * replacement transition for the same property is still active. Because that set\n * is sampled when the wait is armed, a property whose transition only starts a\n * frame later would otherwise be settled over; the still-running set is therefore\n * re-read before completing and adopted when it is not yet empty. Without Web\n * Animations data, explicit transition properties retain the declared-property\n * behavior and the ambiguous `transition-property: all` form waits until the\n * computed maximum wall-clock time. A `max + 50ms` timeout is always armed\n * because browsers emit no terminal event when a property never transitions, an\n * ancestor disappears, or rendering is otherwise interrupted; a consumer with\n * better knowledge can replace that fallback via\n * {@link TransitionWaitOptions.timeoutMs}.\n */\nexport class TransitionCompletion {\n readonly #timers = new SafeTimeout();\n #element: HTMLElement | null = null;\n #complete: (() => void) | null = null;\n #pendingProperties: Set<string> | null = null;\n #deadline = 0;\n\n /**\n * Replaces any prior wait and invokes `complete` synchronously for a 0ms\n * transition (including when `getComputedStyle` is unavailable).\n *\n * With a positive `options.timeoutMs` the synchronous fast-path is skipped and\n * the override replaces the auto-computed fallback (see {@link TransitionWaitOptions}).\n */\n wait(element: HTMLElement, complete: () => void, options: TransitionWaitOptions = {}): void {\n this.cancel();\n\n const requested = options.timeoutMs ?? 0;\n const override = Number.isFinite(requested) && requested > 0 ? requested : 0;\n // A non-browser / partial-DOM environment cannot report a computable\n // transition, which is indistinguishable from a 0ms one.\n const timings =\n typeof window.getComputedStyle === \"function\"\n ? transitionTimings(window.getComputedStyle(element))\n : [];\n const maximum = maxTotalMs(timings);\n if (maximum <= 0 && override <= 0) {\n complete();\n return;\n }\n\n this.#element = element;\n this.#complete = complete;\n this.#deadline = Date.now() + maximum;\n const activeProperties = this.#activeTransitionProperties(element);\n this.#pendingProperties =\n activeProperties.size > 0 ? activeProperties : this.#explicitPendingProperties(timings);\n element.addEventListener(\"transitionend\", this.#onTerminal);\n element.addEventListener(\"transitioncancel\", this.#onTerminal);\n this.#timers.set(() => this.#finish(), override > 0 ? override : maximum + 50);\n }\n\n /** Cancels the pending wait without invoking its completion callback. */\n cancel(): void {\n this.#complete = null;\n this.#teardown();\n }\n\n /**\n * Handles terminal events from the observed element only.\n *\n * For explicit property lists, every declared positive-time property must\n * settle. For `all`, no reliable property set exists, so an event can finish\n * only after the computed maximum time; the safety timeout owns the usual path.\n */\n readonly #onTerminal = (event: Event): void => {\n if (event.target !== this.#element) return;\n const transitionEvent = event as TransitionEvent;\n // A pseudo-element's transition is reported with its originating element\n // as `target`; it must not settle the element's own transition.\n if (transitionEvent.pseudoElement) return;\n\n if (this.#pendingProperties) {\n const propertyName = transitionEvent.propertyName;\n const active = this.#activeTransitionProperties(this.#element);\n // An interrupted transition queues its cancel event before the replacement\n // phase starts, but dispatch can happen after the new wait is armed. The\n // replacement is then the active transition for this property, so the stale\n // terminal event must not settle the new phase.\n if (active.has(propertyName)) return;\n if (!this.#pendingProperties.delete(propertyName)) return;\n if (this.#pendingProperties.size > 0) return;\n // The tracked set is an arm-time snapshot, so a transition the phase starts\n // one frame later is absent from it. Re-reading the still-running set before\n // settling adopts those stragglers instead of completing over them; the\n // fallback timer stays armed, so this cannot extend the wait unboundedly.\n if (active.size > 0) {\n this.#pendingProperties = active;\n return;\n }\n this.#finish();\n return;\n }\n\n if (Date.now() >= this.#deadline) this.#finish();\n };\n\n /**\n * Returns active CSS transition properties expanded to the names reported by\n * terminal events. CSS animations and pseudo-element effects are excluded.\n */\n #activeTransitionProperties(element: HTMLElement | null): Set<string> {\n if (!element || typeof element.getAnimations !== \"function\") return new Set();\n\n try {\n const properties = element.getAnimations().flatMap((animation) => {\n if (animation.playState === \"idle\" || animation.playState === \"finished\") return [];\n const effect = animation.effect as KeyframeEffect | null;\n // A pseudo-element effect names the originating element as its target, so\n // `target` alone cannot separate it. Its terminal events are ignored, so\n // tracking its properties would only strand the wait on the fallback.\n if (effect?.pseudoElement) return [];\n const target = effect?.target;\n if (target && target !== element) return [];\n const property = (animation as Partial<CSSTransition>).transitionProperty;\n return typeof property === \"string\" && property.length > 0 ? [property] : [];\n });\n return new Set(properties);\n } catch {\n // Partial DOM implementations can expose getAnimations without supporting\n // it. The declared-property tracker and bounded timer remain safe fallbacks.\n return new Set();\n }\n }\n\n /** Returns explicit positive-time properties, or `null` for the ambiguous `all`. */\n #explicitPendingProperties(timings: TransitionTiming[]): Set<string> | null {\n if (timings.some(({ property }) => property === \"all\")) return null;\n const pending = new Set(\n timings.filter(({ totalMs }) => totalMs > 0).map(({ property }) => property),\n );\n // An empty set is only reachable through a timeout override (a 0ms transition\n // otherwise completes synchronously). With nothing to track, fall back to the\n // deadline rule so any terminal event can settle the wait, exactly like `all`.\n return pending.size > 0 ? pending : null;\n }\n\n /** Completes exactly once, releasing listeners and the fallback before the callback. */\n #finish(): void {\n const complete = this.#complete;\n if (!complete) return;\n this.#complete = null;\n this.#teardown();\n complete();\n }\n\n /** Releases the exact element listeners and timer owned by the current wait. */\n #teardown(): void {\n this.#timers.clearAll();\n this.#element?.removeEventListener(\"transitionend\", this.#onTerminal);\n this.#element?.removeEventListener(\"transitioncancel\", this.#onTerminal);\n this.#element = null;\n this.#pendingProperties = null;\n this.#deadline = 0;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport { maxTransitionTotalMs } from \"../utils/transition_completion\";\n\nconst DELEGATED_EVENTS = [\"click\", \"focusin\", \"focusout\", \"keydown\", \"mouseover\", \"mouseout\"];\n\n/**\n * Headless, highly accessible toast notification behavior.\n *\n * Markup contract (identifier: `stimeo--toast`):\n * <div data-controller=\"stimeo--toast\"\n * data-stimeo--toast-duration-value=\"5000\"\n * data-stimeo--toast-max-value=\"3\">\n * <!-- Attribute-only trigger: no hand-written JS required. -->\n * <button data-action=\"click->stimeo--toast#show\"\n * data-stimeo--toast-body-param=\"Saved\"\n * data-stimeo--toast-type-param=\"status\">Show</button>\n * <!-- The live region is a descendant; the controller element only needs\n * to enclose both the trigger(s) and the list/template targets. -->\n * <div role=\"region\" aria-label=\"Notifications\">\n * <ol data-stimeo--toast-target=\"list\"></ol>\n * <template data-stimeo--toast-target=\"template\">\n * <li data-stimeo--toast-target=\"item\" tabindex=\"0\">\n * <span role=\"status\" data-toast-slot=\"body\"></span>\n * <button type=\"button\" data-toast-dismiss>Dismiss</button>\n * </li>\n * </template>\n * </div>\n * </div>\n *\n * Implements WAI-ARIA live region status/alert announcements, limits simultaneous\n * elements, and pauses dismiss timeouts on hover or focus to comply with WCAG 2.2.1.\n *\n * `dismiss` dispatches `{ item: HTMLElement, reason: \"timeout\" | \"user\" }`.\n *\n * `show` dispatches `{ item: HTMLElement }`.\n *\n * @remarks\n * Behavior only. The controller handles state updates via `data-state` and lifecycle events\n * while leaving visual styling completely to the client's CSS transitions.\n */\nexport class ToastController extends Controller<HTMLElement> {\n static override targets = [\"list\", \"template\", \"item\"];\n static override values = {\n duration: { type: Number, default: 0 },\n max: { type: Number, default: 3 },\n };\n static actions = [\"dismiss\", \"onKeydown\", \"pause\", \"resume\", \"show\"] as const;\n static events = [\"dismiss\", \"show\"] as const;\n\n declare readonly listTarget: HTMLElement;\n declare readonly templateTarget: HTMLTemplateElement;\n declare readonly itemTargets: HTMLElement[];\n declare readonly hasListTarget: boolean;\n declare readonly hasTemplateTarget: boolean;\n\n declare durationValue: number;\n declare maxValue: number;\n\n /**\n * Registry for every auto-dismiss and transition-finalize timer the controller\n * schedules. {@link SafeTimeout} owns *registration and teardown only*; the\n * pause/resume remaining-time accounting stays in `#activeTimeouts` so the\n * per-widget WCAG 2.2.1 semantics are not flattened into the helper.\n */\n #timers = new SafeTimeout();\n\n /**\n * Pending one-shot `requestAnimationFrame` handles (the entering→visible flip).\n * Tracked so {@link disconnect} can cancel any that have not fired, preventing a\n * detached element from being mutated after it leaves the DOM (Turbo).\n */\n #rafHandles = new Map<HTMLElement, number>();\n\n /** Track active timeouts mapped by each toast element for safe cancellation. */\n #activeTimeouts = new Map<HTMLElement, { id: number; startedAt: number; remaining: number }>();\n\n /** Track active pause reasons (hover/focus) per toast for WCAG 2.2.1 pause/resume. */\n #pauseReasons = new Map<HTMLElement, Set<string>>();\n\n /** The stable list that owns delegated listeners for dynamically added items. */\n #delegatedList: HTMLElement | null = null;\n\n override connect(): void {\n this.#connectDelegatedEvents();\n this.enforceMaxLimit();\n for (const item of this.itemTargets) {\n if (!this.#activeTimeouts.has(item) && item.dataset.state !== \"leaving\") {\n this.#startTimer(item);\n }\n }\n }\n\n override disconnect(): void {\n // SafeTimeout owns every auto-dismiss + finalize timer; one call tears them\n // all down so none fires against the detached controller.\n this.#disconnectDelegatedEvents();\n this.#timers.clearAll();\n for (const handle of this.#rafHandles.values()) {\n window.cancelAnimationFrame(handle);\n }\n this.#rafHandles.clear();\n this.#activeTimeouts.clear();\n this.#pauseReasons.clear();\n }\n\n /** Rebinds delegated interaction when Turbo replaces the list target in place. */\n listTargetConnected(element: HTMLElement): void {\n if (this.#delegatedList !== element) this.#connectDelegatedEvents(element);\n }\n\n /** Releases delegation only when the removed target is its current owner. */\n listTargetDisconnected(element: HTMLElement): void {\n if (this.#delegatedList === element) this.#disconnectDelegatedEvents();\n }\n\n durationValueChanged(): void {\n for (const item of this.itemTargets) {\n if (item.dataset.state === \"leaving\") continue;\n\n const pauseReasons = this.#pauseReasons.get(item);\n this.#clearTimer(item);\n if (this.durationValue <= 0) {\n item.removeAttribute(\"data-paused\");\n } else if (pauseReasons && pauseReasons.size > 0) {\n this.#pauseReasons.set(item, pauseReasons);\n this.#activeTimeouts.set(item, {\n id: 0,\n startedAt: 0,\n remaining: this.durationValue,\n });\n item.setAttribute(\"data-paused\", \"true\");\n } else {\n this.#startTimer(item);\n }\n }\n }\n\n maxValueChanged(): void {\n this.enforceMaxLimit();\n }\n\n /**\n * Stimulus lifecycle callback triggered automatically when a new item target\n * enters the DOM. Perfectly handles dynamic client-side injections and server-side\n * Turbo Stream appends alike.\n */\n itemTargetConnected(element: HTMLElement): void {\n this.enforceMaxLimit();\n if (element.dataset.state === \"leaving\" || element.parentNode !== this.listTarget) return;\n\n this.#startTimer(element);\n element.setAttribute(\"data-state\", \"entering\");\n this.#cancelAnimation(element);\n const handle = window.requestAnimationFrame(() => {\n this.#rafHandles.delete(element);\n if (element.parentNode !== this.listTarget || element.dataset.state === \"leaving\") return;\n element.setAttribute(\"data-state\", \"visible\");\n });\n this.#rafHandles.set(element, handle);\n }\n\n /** Clears any active timer when a toast is removed from the DOM. */\n itemTargetDisconnected(element: HTMLElement): void {\n this.#clearTimer(element);\n this.#cancelAnimation(element);\n }\n\n /**\n * Shows a new toast. Accepts its content from either a Stimulus action param\n * (attribute-only trigger) or a programmatic `show` CustomEvent `detail`\n * (remote / Turbo trigger); the action param wins when both are present.\n *\n * <button data-action=\"click->stimeo--toast#show\"\n * data-stimeo--toast-body-param=\"Saved\"\n * data-stimeo--toast-type-param=\"status\">Show</button>\n *\n * element.dispatchEvent(new CustomEvent(\"show\", { detail: { body, type } }))\n *\n * Clones the template slot, interpolates the body text, and appends to the list.\n */\n show(event: Event): void {\n if (!this.hasTemplateTarget || !this.hasListTarget) return;\n\n const body = this.#readField(event, \"body\");\n if (!body) return;\n\n const clone = this.templateTarget.content.cloneNode(true) as DocumentFragment;\n const item = clone.querySelector(\"[data-stimeo--toast-target='item']\") as HTMLElement | null;\n if (!item) return;\n\n const bodySlot = item.querySelector<HTMLElement>(\"[data-toast-slot='body']\");\n if (!bodySlot) return;\n bodySlot.textContent = body;\n\n // Validate the live-region role at runtime; untrusted params/detail could\n // otherwise write an invalid ARIA role. Anything but \"alert\" stays polite.\n bodySlot.setAttribute(\"role\", this.#readField(event, \"type\") === \"alert\" ? \"alert\" : \"status\");\n\n this.listTarget.appendChild(item);\n this.dispatch(\"show\", { detail: { item } });\n }\n\n /**\n * Reads a string field from a Stimulus action param or a CustomEvent `detail`,\n * preferring the action param. Returns null unless a non-empty string is found,\n * so untrusted runtime payloads cannot inject non-string values.\n */\n #readField(event: Event, key: \"body\" | \"type\"): string | null {\n const params = (event as { params?: Record<string, unknown> }).params;\n const fromParams = params?.[key];\n if (typeof fromParams === \"string\" && fromParams.length > 0) return fromParams;\n\n const detail = (event as CustomEvent<unknown>).detail;\n if (detail && typeof detail === \"object\" && key in detail) {\n const value = (detail as Record<string, unknown>)[key];\n if (typeof value === \"string\" && value.length > 0) return value;\n }\n return null;\n }\n\n /** Dismisses the toast that contained the trigger. */\n dismiss(event: Event): void {\n const item = this.#itemFromEvent(event);\n if (!item) return;\n\n this.#removeWithTransition(item, \"user\");\n }\n\n /** Dismisses the focused toast when Escape is pressed. */\n onKeydown(event: KeyboardEvent): void {\n if (event.key === \"Escape\") {\n // Leave a press an inner handler already owned.\n // A press during IME composition (a text field inside the toast) cancels\n // the conversion, never the toast.\n if (event.defaultPrevented || event.isComposing) return;\n const item = this.#itemFromEvent(event);\n if (!item) return;\n\n event.preventDefault();\n this.#removeWithTransition(item, \"user\");\n }\n }\n\n /**\n * Pauses the auto-dismiss timer on mouse entry or keyboard focus.\n *\n * Hover and focus are tracked as independent reasons: the timer is only\n * snapshotted on the first active reason, and only resumed once *every*\n * reason has been released (see {@link resume}). This keeps a toast paused\n * while it is still hovered *or* focused, per WCAG 2.2.1.\n */\n pause(event: Event): void {\n const item = this.#itemFromEvent(event);\n if (!item || this.durationValue <= 0) return;\n\n const timeout = this.#activeTimeouts.get(item);\n if (!timeout) return;\n\n const reasons = this.#pauseReasonsFor(item);\n const wasActive = reasons.size > 0;\n reasons.add(this.#pauseReason(event));\n\n // Only snapshot the remaining time on the first reason; subsequent reasons\n // must not recompute elapsed against the already-cleared timer.\n if (wasActive || timeout.id === 0) return;\n\n this.#timers.clear(timeout.id);\n const elapsed = Date.now() - timeout.startedAt;\n const remaining = Math.max(0, timeout.remaining - elapsed);\n\n if (remaining <= 0) {\n this.#removeWithTransition(item, \"timeout\");\n return;\n }\n\n this.#activeTimeouts.set(item, { id: 0, startedAt: 0, remaining });\n item.setAttribute(\"data-paused\", \"true\");\n }\n\n /** Resumes the auto-dismiss timer once both hover and focus have been released. */\n resume(event: Event): void {\n const item = this.#itemFromEvent(event);\n if (!item || this.durationValue <= 0) return;\n\n const reasons = this.#pauseReasonsFor(item);\n reasons.delete(this.#pauseReason(event));\n // Still paused by the other reason (e.g. mouse left but focus remains).\n if (reasons.size > 0) return;\n\n const timeout = this.#activeTimeouts.get(item);\n if (!timeout) return;\n if (timeout.id !== 0 || timeout.remaining <= 0) return;\n\n item.removeAttribute(\"data-paused\");\n this.#startTimer(item, timeout.remaining);\n }\n\n /** Resolves the toast item element a pause/resume event targets. */\n #itemFromEvent(event: Event): HTMLElement | null {\n const target = event.target instanceof Element ? event.target : event.currentTarget;\n if (!(target instanceof Element) || !this.hasListTarget) return null;\n const item = target.closest<HTMLElement>(\"[data-stimeo--toast-target='item']\");\n return item && this.listTarget.contains(item) ? item : null;\n }\n\n /** Classifies a pause/resume event as a hover or focus reason. */\n #pauseReason(event: Event): \"focus\" | \"hover\" {\n return event.type === \"focusin\" || event.type === \"focusout\" ? \"focus\" : \"hover\";\n }\n\n /** Lazily creates and returns the active pause-reason set for an item. */\n #pauseReasonsFor(item: HTMLElement): Set<string> {\n let reasons = this.#pauseReasons.get(item);\n if (!reasons) {\n reasons = new Set<string>();\n this.#pauseReasons.set(item, reasons);\n }\n return reasons;\n }\n\n #startTimer(element: HTMLElement, duration = this.durationValue): void {\n if (\n duration <= 0 ||\n element.dataset.state === \"leaving\" ||\n element.parentNode !== this.listTarget\n ) {\n return;\n }\n\n this.#clearTimer(element);\n const id = this.#timers.set(() => {\n this.#removeWithTransition(element, \"timeout\");\n }, duration);\n\n this.#activeTimeouts.set(element, { id, startedAt: Date.now(), remaining: duration });\n }\n\n #clearTimer(element: HTMLElement): void {\n const timeout = this.#activeTimeouts.get(element);\n if (timeout) {\n if (timeout.id) this.#timers.clear(timeout.id);\n this.#activeTimeouts.delete(element);\n }\n this.#pauseReasons.delete(element);\n }\n\n #removeWithTransition(element: HTMLElement, reason: \"timeout\" | \"user\"): void {\n if (element.dataset.state === \"leaving\" || element.parentNode !== this.listTarget) return;\n\n this.#clearTimer(element);\n this.#cancelAnimation(element);\n element.setAttribute(\"data-state\", \"leaving\");\n\n const finalize = () => {\n if (element.parentNode !== this.listTarget) return;\n this.listTarget.removeChild(element);\n this.dispatch(\"dismiss\", { detail: { item: element, reason } });\n };\n\n const duration = maxTransitionTotalMs(window.getComputedStyle(element));\n if (duration > 0) {\n this.#timers.set(finalize, duration);\n } else {\n finalize();\n }\n }\n\n /**\n * Removes the oldest toasts when the list exceeds `maxValue`.\n *\n * Public (not `#private`) as a deterministic seam: enforcement normally runs\n * from `itemTargetConnected`, a Stimulus callback delivered through a\n * MutationObserver, which a DOM-only environment does not reliably fire — so\n * it can also be invoked directly. It is not a user-wired action.\n */\n enforceMaxLimit(): void {\n const currentItems = this.itemTargets;\n const max = Math.max(0, this.maxValue);\n if (currentItems.length > max) {\n const excessCount = currentItems.length - max;\n for (let i = 0; i < excessCount; i++) {\n const oldest = currentItems[i];\n if (oldest) this.#removeWithTransition(oldest, \"timeout\");\n }\n }\n }\n\n /** Wires stable-container delegation so newly appended items work immediately. */\n #connectDelegatedEvents(list = this.hasListTarget ? this.listTarget : null): void {\n this.#disconnectDelegatedEvents();\n if (!list) return;\n\n this.#delegatedList = list;\n for (const type of DELEGATED_EVENTS) {\n this.#delegatedList.addEventListener(type, this.#onListEvent);\n }\n }\n\n /** Releases every delegated listener from the exact list that owns it. */\n #disconnectDelegatedEvents(): void {\n if (!this.#delegatedList) return;\n\n for (const type of DELEGATED_EVENTS) {\n this.#delegatedList.removeEventListener(type, this.#onListEvent);\n }\n this.#delegatedList = null;\n }\n\n /** Routes every delegated item interaction from the stable list. */\n readonly #onListEvent = (event: Event): void => {\n if (event.type === \"click\") {\n const target = event.target instanceof Element ? event.target : null;\n const trigger = target?.closest<HTMLElement>(\"[data-toast-dismiss]\");\n if (trigger && this.#delegatedList?.contains(trigger)) this.dismiss(event);\n } else if (event.type === \"keydown\") {\n this.onKeydown(event as KeyboardEvent);\n } else if (this.#crossesItemBoundary(event)) {\n if (event.type === \"focusin\" || event.type === \"mouseover\") this.pause(event);\n else this.resume(event);\n }\n };\n\n /** Whether a bubbling focus/pointer event enters or leaves a toast boundary. */\n #crossesItemBoundary(event: Event): boolean {\n const item = this.#itemFromEvent(event);\n if (!item) return false;\n const related = \"relatedTarget\" in event ? event.relatedTarget : null;\n return !(related instanceof Node && item.contains(related));\n }\n\n /** Cancels the pending entering-to-visible frame owned by one item. */\n #cancelAnimation(element: HTMLElement): void {\n const handle = this.#rafHandles.get(element);\n if (handle === undefined) return;\n window.cancelAnimationFrame(handle);\n this.#rafHandles.delete(element);\n }\n}\n"]}
|
|
@@ -8,10 +8,8 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
8
8
|
* data-stimeo--toggle-group-mode-value="single"
|
|
9
9
|
* role="group" aria-label="Text style">
|
|
10
10
|
* <button type="button" aria-pressed="true" tabindex="0" data-value="bold"
|
|
11
|
-
* data-stimeo--toggle-group-target="item"
|
|
12
|
-
*
|
|
13
|
-
* keydown->stimeo--toggle-group#onKeydown">Bold</button>
|
|
14
|
-
* <!-- more items; exactly one tabindex=0 -->
|
|
11
|
+
* data-stimeo--toggle-group-target="item">Bold</button>
|
|
12
|
+
* <!-- more items; exactly one navigable item has tabindex=0 -->
|
|
15
13
|
* </div>
|
|
16
14
|
*
|
|
17
15
|
* Implements the WAI-ARIA APG **Button (toggle)** pattern with **Toolbar**-style
|
|
@@ -20,17 +18,29 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
20
18
|
* exclusive selection with radio semantics, use
|
|
21
19
|
* {@link RadioGroupController | Radio Group} instead.
|
|
22
20
|
*
|
|
21
|
+
* `change` dispatches `{ value: string, pressed: boolean, values: string[] }`.
|
|
22
|
+
*
|
|
23
23
|
* @remarks
|
|
24
|
-
* Behavior only — the consumer styles off `[aria-pressed="true"]`.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Behavior only — the consumer styles off `[aria-pressed="true"]`. A supported
|
|
25
|
+
* item is either `<button type="button">` or a non-interactive host such as
|
|
26
|
+
* `<div role="button">`; other native interactive elements retain their own
|
|
27
|
+
* activation model and this controller stands down on them.
|
|
28
|
+
*
|
|
29
|
+
* Click handling and keydown handling are delegated from the group, so items
|
|
30
|
+
* added at runtime need only the `item` target. Per-item `data-action` bindings
|
|
31
|
+
* to {@link toggle} and {@link onKeydown} remain supported and are idempotent
|
|
32
|
+
* when combined with delegation.
|
|
27
33
|
*
|
|
28
34
|
* Behavior provided:
|
|
29
35
|
* - Click (or Space/Enter) toggles an item. In `single` mode pressing one item
|
|
30
36
|
* releases the others (0 or 1 pressed); `multiple` allows any number.
|
|
31
|
-
* - `ArrowRight`/`ArrowDown` move focus to the next item,
|
|
32
|
-
* to the previous (wrapping); `Home`/`End` to the
|
|
33
|
-
* -
|
|
37
|
+
* - `ArrowRight`/`ArrowDown` move focus to the next navigable item,
|
|
38
|
+
* `ArrowLeft`/`ArrowUp` to the previous (wrapping); `Home`/`End` to the
|
|
39
|
+
* first/last. Native-disabled and hidden items are skipped. ARIA-disabled
|
|
40
|
+
* items remain discoverable in the roving order but cannot be activated.
|
|
41
|
+
* - `stimeo--toggle-group:change` is dispatched only for user activation. Its
|
|
42
|
+
* detail is `{ value: string, pressed: boolean, values: string[] }`, where
|
|
43
|
+
* `values` is the DOM-ordered value list of every item left pressed.
|
|
34
44
|
*/
|
|
35
45
|
declare class ToggleGroupController extends Controller<HTMLElement> {
|
|
36
46
|
#private;
|
|
@@ -45,11 +55,29 @@ declare class ToggleGroupController extends Controller<HTMLElement> {
|
|
|
45
55
|
static events: readonly ["change"];
|
|
46
56
|
readonly itemTargets: HTMLElement[];
|
|
47
57
|
modeValue: string;
|
|
48
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Normalizes state and establishes one roving entry point. A single authored
|
|
60
|
+
* `tabindex="0"` survives reconnect; otherwise the first pressed, navigable
|
|
61
|
+
* item wins, falling back to the first navigable item.
|
|
62
|
+
*/
|
|
49
63
|
connect(): void;
|
|
50
|
-
/**
|
|
64
|
+
/** Releases every listener and observer while retaining DOM state for Turbo cache/reconnect. */
|
|
65
|
+
disconnect(): void;
|
|
66
|
+
/** Drops a newly connected item from the Tab sequence before reconciling the group. */
|
|
67
|
+
itemTargetConnected(item: HTMLElement): void;
|
|
68
|
+
/** Restores attributes owned only while an element is an item and repairs the Tab stop. */
|
|
69
|
+
itemTargetDisconnected(item: HTMLElement): void;
|
|
70
|
+
/** Reconciles the single-selection invariant when the Stimulus Value changes. */
|
|
71
|
+
modeValueChanged(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Toggles the action's item. Per-item action wiring is optional because click
|
|
74
|
+
* is also delegated from the group.
|
|
75
|
+
*/
|
|
51
76
|
toggle(event: Event): void;
|
|
52
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Moves focus or activates the action's item. Per-item action wiring is
|
|
79
|
+
* optional because keydown is also delegated from the group.
|
|
80
|
+
*/
|
|
53
81
|
onKeydown(event: KeyboardEvent): void;
|
|
54
82
|
}
|
|
55
83
|
|