@unabridged/midwest 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/README.md +64 -1
  2. package/app/assets/javascript/midwest/index.ts +8 -0
  3. package/app/assets/javascript/midwest/keyboard_navigation.ts +68 -0
  4. package/app/assets/javascript/midwest.js +476 -37
  5. package/app/assets/javascript/midwest.js.map +1 -1
  6. package/app/assets/stylesheets/midwest.css +1 -1
  7. package/app/assets/stylesheets/midwest.tailwind.css +13 -1
  8. package/dist/css/midwest.css +1 -1
  9. package/dist/javascript/collection/app/assets/javascript/midwest/index.js +8 -0
  10. package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
  11. package/dist/javascript/collection/app/assets/javascript/midwest/keyboard_navigation.js +42 -0
  12. package/dist/javascript/collection/app/assets/javascript/midwest/keyboard_navigation.js.map +1 -0
  13. package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js +8 -37
  14. package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js.map +1 -1
  15. package/dist/javascript/collection/app/components/midwest/focus_trap_component/focus_trap_component_controller.js +83 -0
  16. package/dist/javascript/collection/app/components/midwest/focus_trap_component/focus_trap_component_controller.js.map +1 -0
  17. package/dist/javascript/collection/app/components/midwest/form/switch_component/theme_switch_controller.js +42 -0
  18. package/dist/javascript/collection/app/components/midwest/form/switch_component/theme_switch_controller.js.map +1 -0
  19. package/dist/javascript/collection/app/components/midwest/intersection_observer_component/intersection_observer_component_controller.js +43 -0
  20. package/dist/javascript/collection/app/components/midwest/intersection_observer_component/intersection_observer_component_controller.js.map +1 -0
  21. package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js +273 -0
  22. package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js.map +1 -0
  23. package/dist/javascript/midwest.js +476 -37
  24. package/dist/javascript/midwest.js.map +1 -1
  25. package/package.json +1 -1
@@ -32,6 +32,10 @@ import PasswordRequirements from '../../../components/midwest/password_requireme
32
32
  import CodeBlock from '../../../components/midwest/code_block_component/code_block_component_controller.js';
33
33
  import DatePicker from '../../../components/midwest/form/date_picker_component/date_picker_component_controller.js';
34
34
  import TimePicker from '../../../components/midwest/form/time_picker_component/time_picker_component_controller.js';
35
+ import ViewportObserver from '../../../components/midwest/intersection_observer_component/intersection_observer_component_controller.js';
36
+ import FocusTrap from '../../../components/midwest/focus_trap_component/focus_trap_component_controller.js';
37
+ import Onboarding from '../../../components/midwest/onboarding_component/onboarding_component_controller.js';
38
+ import ThemeSwitch from '../../../components/midwest/form/switch_component/theme_switch_controller.js';
35
39
 
36
40
  function registerMidwestControllers(application) {
37
41
  application.register("midwest-card", Card);
@@ -68,6 +72,10 @@ function registerMidwestControllers(application) {
68
72
  application.register("midwest-code-block", CodeBlock);
69
73
  application.register("midwest-date-picker", DatePicker);
70
74
  application.register("midwest-time-picker", TimePicker);
75
+ application.register("midwest-intersection-observer", ViewportObserver);
76
+ application.register("midwest-focus-trap", FocusTrap);
77
+ application.register("midwest-onboarding", Onboarding);
78
+ application.register("midwest-theme-switch", ThemeSwitch);
71
79
  }
72
80
 
73
81
  export { Banner, Card, Chart, CountdownTimer, registerMidwestControllers };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../../../../app/assets/javascript/midwest/index.ts"],"sourcesContent":["import Card from '../../../components/midwest/card_component/card_component_controller'\nimport Banner from '../../../components/midwest/banner_component/banner_component_controller'\nimport Input from '../../../components/midwest/form/input_component/input_component_controller'\nimport File from '../../../components/midwest/form/file_component/file_component_controller'\nimport Dropdown from '../../../components/midwest/dropdown_component/dropdown_component_controller'\nimport Prose from '../../../components/midwest/prose_component/prose_component_controller'\nimport Dialog from '../../../components/midwest/dialog_component/dialog_component_controller'\nimport Tabs from '../../../components/midwest/tabs_component/tabs_component_controller'\nimport Tooltip from '../../../components/midwest/tooltip_component/tooltip_component_controller'\nimport Carousel from '../../../components/midwest/carousel_component/carousel_component_controller'\nimport FormLiveSummary from '../../../components/midwest/form/live_summary_component/live_summary_component_controller'\nimport Form from '../../../components/midwest/form/form_controller'\nimport CountdownTimer from '../../../components/midwest/countdown_timer_component/countdown_timer_component_controller'\nimport Badge from '../../../components/midwest/badge_component/badge_component_controller'\nimport Confirmation from '../../../components/midwest/confirmation_component/confirmation_component_controller'\nimport Chart from '../../../components/midwest/chart_component/chart_component_controller'\nimport Range from '../../../components/midwest/form/range_component/range_component_controller'\nimport Rating from '../../../components/midwest/rating_component/rating_component_controller'\nimport Autocomplete from '../../../components/midwest/form/autocomplete_component/autocomplete_component_controller'\nimport Image from '../../../components/midwest/image_component/image_component_controller'\nimport ColorPicker from '../../../components/midwest/form/color_picker_component/color_picker_component_controller'\nimport Notification from '../../../components/midwest/notification_component/notification_component_controller'\nimport HorizontalScroll from '../../../components/midwest/horizontal_scroll_component/horizontal_scroll_component_controller'\nimport Table from '../../../components/midwest/table_component/table_component_controller'\nimport LoadMore from '../../../components/midwest/table_component/load_more_controller'\nimport Pagination from '../../../components/midwest/pagination_component/pagination_component_controller'\nimport ShowIf from '../../../components/midwest/show_if_component/show_if_component_controller'\nimport TreeView from '../../../components/midwest/tree_view_component/tree_view_component_controller'\nimport CommandPalette from '../../../components/midwest/command_palette_component/command_palette_component_controller'\nimport Repeatable from '../../../components/midwest/form/repeatable_component/repeatable_component_controller'\nimport PasswordRequirements from '../../../components/midwest/password_requirements_component/password_requirements_component_controller'\nimport CodeBlock from '../../../components/midwest/code_block_component/code_block_component_controller'\nimport DatePicker from '../../../components/midwest/form/date_picker_component/date_picker_component_controller'\nimport TimePicker from '../../../components/midwest/form/time_picker_component/time_picker_component_controller'\n/* IMPORTS */\n\nexport { Card, Banner, CountdownTimer, Chart }\n\nexport function registerMidwestControllers (application: any) {\n application.register('midwest-card', Card)\n application.register('midwest-banner', Banner)\n application.register('midwest-countdown-timer', CountdownTimer)\n application.register('midwest-input', Input)\n application.register('midwest-file', File)\n application.register('midwest-dropdown', Dropdown)\n application.register('midwest-prose', Prose)\n application.register('midwest-dialog', Dialog)\n application.register('midwest-tabs', Tabs)\n application.register('midwest-tooltip', Tooltip)\n application.register('midwest-carousel', Carousel)\n application.register('midwest-form-live-summary', FormLiveSummary)\n application.register('midwest-form', Form)\n application.register('midwest-badge', Badge)\n application.register('midwest-confirmation', Confirmation)\n application.register('midwest-chart', Chart)\n application.register('midwest-range', Range)\n application.register('midwest-rating', Rating)\n application.register('midwest-autocomplete', Autocomplete)\n application.register('midwest-image', Image)\n application.register('midwest-color-picker', ColorPicker)\n application.register('midwest-notification', Notification)\n application.register('midwest-horizontal-scroll', HorizontalScroll)\n application.register('midwest-table', Table)\n application.register('midwest-load-more', LoadMore)\n application.register('midwest-pagination', Pagination)\n application.register('midwest-show-if', ShowIf)\n application.register('midwest-tree-view', TreeView)\n application.register('midwest-command-palette', CommandPalette)\n application.register('midwest-repeatable', Repeatable)\n application.register('midwest-password-requirements', PasswordRequirements)\n application.register('midwest-code-block', CodeBlock)\n application.register('midwest-date-picker', DatePicker)\n application.register('midwest-time-picker', TimePicker)\n /* EXPORTS */\n}\n"],"names":["Confirmation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCO,SAAS,2BAA4B,WAAkB,EAAA;AAC5D,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,2BAA2B,cAAc,CAAA,CAAA;AAC9D,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,oBAAoB,QAAQ,CAAA,CAAA;AACjD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,mBAAmB,OAAO,CAAA,CAAA;AAC/C,EAAY,WAAA,CAAA,QAAA,CAAS,oBAAoB,QAAQ,CAAA,CAAA;AACjD,EAAY,WAAA,CAAA,QAAA,CAAS,6BAA6B,eAAe,CAAA,CAAA;AACjE,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwBA,sBAAY,CAAA,CAAA;AACzD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,YAAY,CAAA,CAAA;AACzD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,WAAW,CAAA,CAAA;AACxD,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,YAAY,CAAA,CAAA;AACzD,EAAY,WAAA,CAAA,QAAA,CAAS,6BAA6B,gBAAgB,CAAA,CAAA;AAClE,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,qBAAqB,QAAQ,CAAA,CAAA;AAClD,EAAY,WAAA,CAAA,QAAA,CAAS,sBAAsB,UAAU,CAAA,CAAA;AACrD,EAAY,WAAA,CAAA,QAAA,CAAS,mBAAmB,MAAM,CAAA,CAAA;AAC9C,EAAY,WAAA,CAAA,QAAA,CAAS,qBAAqB,QAAQ,CAAA,CAAA;AAClD,EAAY,WAAA,CAAA,QAAA,CAAS,2BAA2B,cAAc,CAAA,CAAA;AAC9D,EAAY,WAAA,CAAA,QAAA,CAAS,sBAAsB,UAAU,CAAA,CAAA;AACrD,EAAY,WAAA,CAAA,QAAA,CAAS,iCAAiC,oBAAoB,CAAA,CAAA;AAC1E,EAAY,WAAA,CAAA,QAAA,CAAS,sBAAsB,SAAS,CAAA,CAAA;AACpD,EAAY,WAAA,CAAA,QAAA,CAAS,uBAAuB,UAAU,CAAA,CAAA;AACtD,EAAY,WAAA,CAAA,QAAA,CAAS,uBAAuB,UAAU,CAAA,CAAA;AAExD;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../../../../../app/assets/javascript/midwest/index.ts"],"sourcesContent":["import Card from '../../../components/midwest/card_component/card_component_controller'\nimport Banner from '../../../components/midwest/banner_component/banner_component_controller'\nimport Input from '../../../components/midwest/form/input_component/input_component_controller'\nimport File from '../../../components/midwest/form/file_component/file_component_controller'\nimport Dropdown from '../../../components/midwest/dropdown_component/dropdown_component_controller'\nimport Prose from '../../../components/midwest/prose_component/prose_component_controller'\nimport Dialog from '../../../components/midwest/dialog_component/dialog_component_controller'\nimport Tabs from '../../../components/midwest/tabs_component/tabs_component_controller'\nimport Tooltip from '../../../components/midwest/tooltip_component/tooltip_component_controller'\nimport Carousel from '../../../components/midwest/carousel_component/carousel_component_controller'\nimport FormLiveSummary from '../../../components/midwest/form/live_summary_component/live_summary_component_controller'\nimport Form from '../../../components/midwest/form/form_controller'\nimport CountdownTimer from '../../../components/midwest/countdown_timer_component/countdown_timer_component_controller'\nimport Badge from '../../../components/midwest/badge_component/badge_component_controller'\nimport Confirmation from '../../../components/midwest/confirmation_component/confirmation_component_controller'\nimport Chart from '../../../components/midwest/chart_component/chart_component_controller'\nimport Range from '../../../components/midwest/form/range_component/range_component_controller'\nimport Rating from '../../../components/midwest/rating_component/rating_component_controller'\nimport Autocomplete from '../../../components/midwest/form/autocomplete_component/autocomplete_component_controller'\nimport Image from '../../../components/midwest/image_component/image_component_controller'\nimport ColorPicker from '../../../components/midwest/form/color_picker_component/color_picker_component_controller'\nimport Notification from '../../../components/midwest/notification_component/notification_component_controller'\nimport HorizontalScroll from '../../../components/midwest/horizontal_scroll_component/horizontal_scroll_component_controller'\nimport Table from '../../../components/midwest/table_component/table_component_controller'\nimport LoadMore from '../../../components/midwest/table_component/load_more_controller'\nimport Pagination from '../../../components/midwest/pagination_component/pagination_component_controller'\nimport ShowIf from '../../../components/midwest/show_if_component/show_if_component_controller'\nimport TreeView from '../../../components/midwest/tree_view_component/tree_view_component_controller'\nimport CommandPalette from '../../../components/midwest/command_palette_component/command_palette_component_controller'\nimport Repeatable from '../../../components/midwest/form/repeatable_component/repeatable_component_controller'\nimport PasswordRequirements from '../../../components/midwest/password_requirements_component/password_requirements_component_controller'\nimport CodeBlock from '../../../components/midwest/code_block_component/code_block_component_controller'\nimport DatePicker from '../../../components/midwest/form/date_picker_component/date_picker_component_controller'\nimport TimePicker from '../../../components/midwest/form/time_picker_component/time_picker_component_controller'\nimport IntersectionObserver from '../../../components/midwest/intersection_observer_component/intersection_observer_component_controller'\nimport FocusTrap from '../../../components/midwest/focus_trap_component/focus_trap_component_controller'\nimport Onboarding from '../../../components/midwest/onboarding_component/onboarding_component_controller'\nimport ThemeSwitch from '../../../components/midwest/form/switch_component/theme_switch_controller'\n/* IMPORTS */\n\nexport { Card, Banner, CountdownTimer, Chart }\n\nexport function registerMidwestControllers (application: any) {\n application.register('midwest-card', Card)\n application.register('midwest-banner', Banner)\n application.register('midwest-countdown-timer', CountdownTimer)\n application.register('midwest-input', Input)\n application.register('midwest-file', File)\n application.register('midwest-dropdown', Dropdown)\n application.register('midwest-prose', Prose)\n application.register('midwest-dialog', Dialog)\n application.register('midwest-tabs', Tabs)\n application.register('midwest-tooltip', Tooltip)\n application.register('midwest-carousel', Carousel)\n application.register('midwest-form-live-summary', FormLiveSummary)\n application.register('midwest-form', Form)\n application.register('midwest-badge', Badge)\n application.register('midwest-confirmation', Confirmation)\n application.register('midwest-chart', Chart)\n application.register('midwest-range', Range)\n application.register('midwest-rating', Rating)\n application.register('midwest-autocomplete', Autocomplete)\n application.register('midwest-image', Image)\n application.register('midwest-color-picker', ColorPicker)\n application.register('midwest-notification', Notification)\n application.register('midwest-horizontal-scroll', HorizontalScroll)\n application.register('midwest-table', Table)\n application.register('midwest-load-more', LoadMore)\n application.register('midwest-pagination', Pagination)\n application.register('midwest-show-if', ShowIf)\n application.register('midwest-tree-view', TreeView)\n application.register('midwest-command-palette', CommandPalette)\n application.register('midwest-repeatable', Repeatable)\n application.register('midwest-password-requirements', PasswordRequirements)\n application.register('midwest-code-block', CodeBlock)\n application.register('midwest-date-picker', DatePicker)\n application.register('midwest-time-picker', TimePicker)\n application.register('midwest-intersection-observer', IntersectionObserver)\n application.register('midwest-focus-trap', FocusTrap)\n application.register('midwest-onboarding', Onboarding)\n application.register('midwest-theme-switch', ThemeSwitch)\n /* EXPORTS */\n}\n"],"names":["Confirmation","IntersectionObserver"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ const NAVIGATION_KEYS = ["ArrowDown", "ArrowUp", "Home", "End", "Escape", "Tab"];
2
+ function handleRovingFocusKeydown(event, items, options = {}) {
3
+ if (items.length === 0)
4
+ return false;
5
+ if (!NAVIGATION_KEYS.includes(event.key))
6
+ return false;
7
+ const { onEscape, onTab, loop = true } = options;
8
+ const current = items.findIndex((item) => item === document.activeElement);
9
+ switch (event.key) {
10
+ case "ArrowDown": {
11
+ event.preventDefault();
12
+ const next = current < items.length - 1 ? current + 1 : loop ? 0 : items.length - 1;
13
+ items[next].focus();
14
+ break;
15
+ }
16
+ case "ArrowUp": {
17
+ event.preventDefault();
18
+ const prev = current > 0 ? current - 1 : loop ? items.length - 1 : 0;
19
+ items[prev].focus();
20
+ break;
21
+ }
22
+ case "Home":
23
+ event.preventDefault();
24
+ items[0].focus();
25
+ break;
26
+ case "End":
27
+ event.preventDefault();
28
+ items[items.length - 1].focus();
29
+ break;
30
+ case "Escape":
31
+ event.preventDefault();
32
+ onEscape?.();
33
+ break;
34
+ case "Tab":
35
+ onTab?.();
36
+ break;
37
+ }
38
+ return true;
39
+ }
40
+
41
+ export { handleRovingFocusKeydown };
42
+ //# sourceMappingURL=keyboard_navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard_navigation.js","sources":["../../../../../../../app/assets/javascript/midwest/keyboard_navigation.ts"],"sourcesContent":["// Shared keyboard-navigation helpers for menu/list components.\n//\n// Implements the WAI-ARIA \"roving focus\" pattern: arrow keys move DOM focus\n// between a flat list of items, Home/End jump to the ends, and Escape/Tab hand\n// control back to the caller (typically to close a popover and restore focus).\n// Components own *which* elements are navigable and *what* closing means; this\n// module owns the key handling so the behavior stays consistent across the\n// library.\n\nconst NAVIGATION_KEYS = ['ArrowDown', 'ArrowUp', 'Home', 'End', 'Escape', 'Tab']\n\nexport interface RovingFocusOptions {\n // Called on Escape. Typically closes the menu and returns focus to the trigger.\n onEscape?: () => void\n // Called on Tab. The default Tab behavior is left intact so focus still leaves\n // the menu; use this to close the menu alongside it.\n onTab?: () => void\n // When true (default) arrow navigation wraps from the last item to the first\n // and vice versa. When false it stops at the ends.\n loop?: boolean\n}\n\n// Handles a keydown against a roving-focus list. Returns true when the event\n// was one this helper is responsible for, so callers can `stopPropagation()`\n// only for keys that were actually handled.\nexport function handleRovingFocusKeydown (\n event: KeyboardEvent,\n items: HTMLElement[],\n options: RovingFocusOptions = {}\n): boolean {\n if (items.length === 0) return false\n if (!NAVIGATION_KEYS.includes(event.key)) return false\n\n const { onEscape, onTab, loop = true } = options\n const current = items.findIndex(item => item === document.activeElement)\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault()\n const next = current < items.length - 1 ? current + 1 : (loop ? 0 : items.length - 1)\n items[next].focus()\n break\n }\n case 'ArrowUp': {\n event.preventDefault()\n const prev = current > 0 ? current - 1 : (loop ? items.length - 1 : 0)\n items[prev].focus()\n break\n }\n case 'Home':\n event.preventDefault()\n items[0].focus()\n break\n case 'End':\n event.preventDefault()\n items[items.length - 1].focus()\n break\n case 'Escape':\n event.preventDefault()\n onEscape?.()\n break\n case 'Tab':\n onTab?.()\n break\n }\n\n return true\n}\n"],"names":[],"mappings":"AASA,MAAM,kBAAkB,CAAC,WAAA,EAAa,WAAW,MAAQ,EAAA,KAAA,EAAO,UAAU,KAAK,CAAA,CAAA;AAgBxE,SAAS,wBACd,CAAA,KAAA,EACA,KACA,EAAA,OAAA,GAA8B,EACrB,EAAA;AACT,EAAA,IAAI,MAAM,MAAW,KAAA,CAAA;AAAG,IAAO,OAAA,KAAA,CAAA;AAC/B,EAAA,IAAI,CAAC,eAAA,CAAgB,QAAS,CAAA,KAAA,CAAM,GAAG,CAAA;AAAG,IAAO,OAAA,KAAA,CAAA;AAEjD,EAAA,MAAM,EAAE,QAAA,EAAU,KAAO,EAAA,IAAA,GAAO,MAAS,GAAA,OAAA,CAAA;AACzC,EAAA,MAAM,UAAU,KAAM,CAAA,SAAA,CAAU,CAAQ,IAAA,KAAA,IAAA,KAAS,SAAS,aAAa,CAAA,CAAA;AAEvE,EAAA,QAAQ,MAAM,GAAK;AAAA,IACjB,KAAK,WAAa,EAAA;AAChB,MAAA,KAAA,CAAM,cAAe,EAAA,CAAA;AACrB,MAAM,MAAA,IAAA,GAAO,OAAU,GAAA,KAAA,CAAM,MAAS,GAAA,CAAA,GAAI,UAAU,CAAK,GAAA,IAAA,GAAO,CAAI,GAAA,KAAA,CAAM,MAAS,GAAA,CAAA,CAAA;AACnF,MAAM,KAAA,CAAA,IAAI,EAAE,KAAM,EAAA,CAAA;AAClB,MAAA,MAAA;AAAA,KACF;AAAA,IACA,KAAK,SAAW,EAAA;AACd,MAAA,KAAA,CAAM,cAAe,EAAA,CAAA;AACrB,MAAM,MAAA,IAAA,GAAO,UAAU,CAAI,GAAA,OAAA,GAAU,IAAK,IAAO,GAAA,KAAA,CAAM,SAAS,CAAI,GAAA,CAAA,CAAA;AACpE,MAAM,KAAA,CAAA,IAAI,EAAE,KAAM,EAAA,CAAA;AAClB,MAAA,MAAA;AAAA,KACF;AAAA,IACA,KAAK,MAAA;AACH,MAAA,KAAA,CAAM,cAAe,EAAA,CAAA;AACrB,MAAM,KAAA,CAAA,CAAC,EAAE,KAAM,EAAA,CAAA;AACf,MAAA,MAAA;AAAA,IACF,KAAK,KAAA;AACH,MAAA,KAAA,CAAM,cAAe,EAAA,CAAA;AACrB,MAAA,KAAA,CAAM,KAAM,CAAA,MAAA,GAAS,CAAC,CAAA,CAAE,KAAM,EAAA,CAAA;AAC9B,MAAA,MAAA;AAAA,IACF,KAAK,QAAA;AACH,MAAA,KAAA,CAAM,cAAe,EAAA,CAAA;AACrB,MAAW,QAAA,IAAA,CAAA;AACX,MAAA,MAAA;AAAA,IACF,KAAK,KAAA;AACH,MAAQ,KAAA,IAAA,CAAA;AACR,MAAA,MAAA;AAAA,GACJ;AAEA,EAAO,OAAA,IAAA,CAAA;AACT;;;;"}
@@ -1,5 +1,6 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
  import { autoUpdate, computePosition, hide, offset, autoPlacement, shift } from '../../../../node_modules/@floating-ui/dom/dist/floating-ui.dom.js';
3
+ import { handleRovingFocusKeydown } from '../../../assets/javascript/midwest/keyboard_navigation.js';
3
4
 
4
5
  class Dropdown extends Controller {
5
6
  static values = {
@@ -54,45 +55,15 @@ class Dropdown extends Controller {
54
55
  }
55
56
  };
56
57
  handleKeydown = (event) => {
57
- const items = this.menuItems;
58
- if (items.length === 0)
59
- return;
60
- if (!["ArrowDown", "ArrowUp", "Home", "End", "Escape", "Tab"].includes(event.key))
61
- return;
62
- event.stopPropagation();
63
- const currentIndex = items.findIndex((item) => item === document.activeElement);
64
- switch (event.key) {
65
- case "ArrowDown": {
66
- event.preventDefault();
67
- const next = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
68
- items[next].focus();
69
- break;
70
- }
71
- case "ArrowUp": {
72
- event.preventDefault();
73
- const prev = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
74
- items[prev].focus();
75
- break;
76
- }
77
- case "Home":
78
- event.preventDefault();
79
- items[0].focus();
80
- break;
81
- case "End":
82
- event.preventDefault();
83
- items[items.length - 1].focus();
84
- break;
85
- case "Escape":
86
- event.preventDefault();
58
+ const handled = handleRovingFocusKeydown(event, this.menuItems, {
59
+ onEscape: () => {
87
60
  this.element.hidePopover();
88
61
  requestAnimationFrame(() => this.buttonEl?.focus());
89
- break;
90
- case "Tab":
91
- this.element.hidePopover();
92
- break;
93
- default:
94
- return;
95
- }
62
+ },
63
+ onTab: () => this.element.hidePopover()
64
+ });
65
+ if (handled)
66
+ event.stopPropagation();
96
67
  };
97
68
  get menuItems() {
98
69
  return Array.from(
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown_component_controller.js","sources":["../../../../../../../app/components/midwest/dropdown_component/dropdown_component_controller.ts"],"sourcesContent":["\nimport { Controller } from '@hotwired/stimulus'\n\nimport {\n computePosition,\n autoPlacement,\n shift,\n offset,\n autoUpdate,\n hide,\n Placement\n} from '@floating-ui/dom'\n\nexport default class Dropdown extends Controller<HTMLFormElement> {\n static values = {\n computed: { type: Boolean, default: false },\n supported: { type: Boolean, default: true },\n align: { type: String, default: 'right' }\n }\n\n declare computedValue: boolean\n declare supportedValue: boolean\n declare alignValue: Placement\n declare readonly hasComputedValue: boolean\n declare readonly hasSupportedValue: boolean\n declare readonly hasAlignValue: boolean\n\n observer = new IntersectionObserver(this.computedCB.bind(this), { threshold: 1 })\n\n cleanup: any\n\n connect () {\n this.supportedValue = CSS.supports('anchor-name: --myanchor')\n\n if (this.isComputed) {\n this.buttonEl.addEventListener('click', this.unsupportedClickCB)\n this.cleanup = autoUpdate(this.buttonEl, this.element, async () => await this.computation())\n }\n\n this.element.addEventListener('toggle', this.addOpenedClass)\n this.element.addEventListener('turbo:click', this.closeIfOpen)\n this.element.addEventListener('popover:close', this.closeIfOpen)\n }\n\n disconnect () {\n if (this.isComputed) {\n this.cleanup()\n }\n\n this.buttonEl.removeEventListener('click', this.unsupportedClickCB)\n this.element.removeEventListener('toggle', this.addOpenedClass)\n this.element.removeEventListener('keydown', this.handleKeydown)\n this.element.removeEventListener('turbo:click', this.closeIfOpen)\n this.element.removeEventListener('popover:close', this.closeIfOpen)\n }\n\n unsupportedClickCB = (event: MouseEvent) => {\n event.preventDefault()\n event.stopPropagation()\n this.element.togglePopover()\n }\n\n get buttonEl () {\n return this.element.parentElement?.querySelector(\n `[popovertarget=\"${this.element.id}\"]:not([popovertargetaction=\"hide\"])`\n ) as HTMLButtonElement\n }\n\n get isComputed () {\n return this.computedValue || !this.supportedValue\n }\n\n addOpenedClass = (event: any) => {\n const isOpen = event.newState === 'open'\n this.buttonEl.classList.toggle('opened', isOpen)\n this.buttonEl.setAttribute('aria-expanded', String(isOpen))\n\n if (isOpen) {\n this.element.addEventListener('keydown', this.handleKeydown)\n requestAnimationFrame(() => this.menuItems[0]?.focus())\n } else {\n this.element.removeEventListener('keydown', this.handleKeydown)\n }\n }\n\n private handleKeydown = (event: KeyboardEvent) => {\n const items = this.menuItems\n if (items.length === 0) return\n\n if (!['ArrowDown', 'ArrowUp', 'Home', 'End', 'Escape', 'Tab'].includes(event.key)) return\n\n event.stopPropagation()\n\n const currentIndex = items.findIndex(item => item === document.activeElement)\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault()\n const next = currentIndex < items.length - 1 ? currentIndex + 1 : 0\n items[next].focus()\n break\n }\n case 'ArrowUp': {\n event.preventDefault()\n const prev = currentIndex > 0 ? currentIndex - 1 : items.length - 1\n items[prev].focus()\n break\n }\n case 'Home':\n event.preventDefault()\n items[0].focus()\n break\n case 'End':\n event.preventDefault()\n items[items.length - 1].focus()\n break\n case 'Escape':\n event.preventDefault()\n this.element.hidePopover()\n requestAnimationFrame(() => this.buttonEl?.focus())\n break\n case 'Tab':\n this.element.hidePopover()\n break\n default:\n return\n }\n }\n\n private get menuItems (): HTMLElement[] {\n return Array.from(\n this.element.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"]:not([disabled]):not([aria-disabled=\"true\"])'\n )\n ).filter(item =>\n item.closest('[data-controller~=\"midwest-dropdown\"]') === this.element &&\n getComputedStyle(item).display !== 'none'\n )\n }\n\n computedCB (entries: IntersectionObserverEntry[]) {\n entries.forEach(async () => {\n this.computation()\n })\n }\n\n closeIfOpen = () => {\n this.element.hidePopover()\n }\n\n async computation () {\n if (!this.buttonEl) return\n if (!this.isComputed) return\n\n const { x, y, middlewareData } = await computePosition(this.buttonEl, this.element, {\n strategy: 'fixed',\n placement: this.alignValue,\n middleware: [\n hide(),\n offset(6),\n autoPlacement({\n allowedPlacements: [\n 'left-start',\n 'left-end',\n 'right-start',\n 'right-end',\n 'bottom-end',\n 'bottom-start'\n ],\n crossAxis: true\n }),\n shift({ padding: 24 })\n ]\n })\n\n if (!middlewareData || (middlewareData.hide == null)) return\n\n Object.assign(this.element.style, {\n left: `${x}px`,\n top: `${y}px`,\n visibility: middlewareData.hide.referenceHidden ? 'hidden' : 'visible'\n })\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dropdown_component_controller.js","sources":["../../../../../../../app/components/midwest/dropdown_component/dropdown_component_controller.ts"],"sourcesContent":["\nimport { Controller } from '@hotwired/stimulus'\n\nimport {\n computePosition,\n autoPlacement,\n shift,\n offset,\n autoUpdate,\n hide,\n Placement\n} from '@floating-ui/dom'\n\nimport { handleRovingFocusKeydown } from '../../../assets/javascript/midwest/keyboard_navigation'\n\nexport default class Dropdown extends Controller<HTMLFormElement> {\n static values = {\n computed: { type: Boolean, default: false },\n supported: { type: Boolean, default: true },\n align: { type: String, default: 'right' }\n }\n\n declare computedValue: boolean\n declare supportedValue: boolean\n declare alignValue: Placement\n declare readonly hasComputedValue: boolean\n declare readonly hasSupportedValue: boolean\n declare readonly hasAlignValue: boolean\n\n observer = new IntersectionObserver(this.computedCB.bind(this), { threshold: 1 })\n\n cleanup: any\n\n connect () {\n this.supportedValue = CSS.supports('anchor-name: --myanchor')\n\n if (this.isComputed) {\n this.buttonEl.addEventListener('click', this.unsupportedClickCB)\n this.cleanup = autoUpdate(this.buttonEl, this.element, async () => await this.computation())\n }\n\n this.element.addEventListener('toggle', this.addOpenedClass)\n this.element.addEventListener('turbo:click', this.closeIfOpen)\n this.element.addEventListener('popover:close', this.closeIfOpen)\n }\n\n disconnect () {\n if (this.isComputed) {\n this.cleanup()\n }\n\n this.buttonEl.removeEventListener('click', this.unsupportedClickCB)\n this.element.removeEventListener('toggle', this.addOpenedClass)\n this.element.removeEventListener('keydown', this.handleKeydown)\n this.element.removeEventListener('turbo:click', this.closeIfOpen)\n this.element.removeEventListener('popover:close', this.closeIfOpen)\n }\n\n unsupportedClickCB = (event: MouseEvent) => {\n event.preventDefault()\n event.stopPropagation()\n this.element.togglePopover()\n }\n\n get buttonEl () {\n return this.element.parentElement?.querySelector(\n `[popovertarget=\"${this.element.id}\"]:not([popovertargetaction=\"hide\"])`\n ) as HTMLButtonElement\n }\n\n get isComputed () {\n return this.computedValue || !this.supportedValue\n }\n\n addOpenedClass = (event: any) => {\n const isOpen = event.newState === 'open'\n this.buttonEl.classList.toggle('opened', isOpen)\n this.buttonEl.setAttribute('aria-expanded', String(isOpen))\n\n if (isOpen) {\n this.element.addEventListener('keydown', this.handleKeydown)\n requestAnimationFrame(() => this.menuItems[0]?.focus())\n } else {\n this.element.removeEventListener('keydown', this.handleKeydown)\n }\n }\n\n private handleKeydown = (event: KeyboardEvent) => {\n const handled = handleRovingFocusKeydown(event, this.menuItems, {\n onEscape: () => {\n this.element.hidePopover()\n requestAnimationFrame(() => this.buttonEl?.focus())\n },\n onTab: () => this.element.hidePopover()\n })\n\n if (handled) event.stopPropagation()\n }\n\n private get menuItems (): HTMLElement[] {\n return Array.from(\n this.element.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"]:not([disabled]):not([aria-disabled=\"true\"])'\n )\n ).filter(item =>\n item.closest('[data-controller~=\"midwest-dropdown\"]') === this.element &&\n getComputedStyle(item).display !== 'none'\n )\n }\n\n computedCB (entries: IntersectionObserverEntry[]) {\n entries.forEach(async () => {\n this.computation()\n })\n }\n\n closeIfOpen = () => {\n this.element.hidePopover()\n }\n\n async computation () {\n if (!this.buttonEl) return\n if (!this.isComputed) return\n\n const { x, y, middlewareData } = await computePosition(this.buttonEl, this.element, {\n strategy: 'fixed',\n placement: this.alignValue,\n middleware: [\n hide(),\n offset(6),\n autoPlacement({\n allowedPlacements: [\n 'left-start',\n 'left-end',\n 'right-start',\n 'right-end',\n 'bottom-end',\n 'bottom-start'\n ],\n crossAxis: true\n }),\n shift({ padding: 24 })\n ]\n })\n\n if (!middlewareData || (middlewareData.hide == null)) return\n\n Object.assign(this.element.style, {\n left: `${x}px`,\n top: `${y}px`,\n visibility: middlewareData.hide.referenceHidden ? 'hidden' : 'visible'\n })\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ const FOCUSABLE = [
4
+ "a[href]",
5
+ "button:not([disabled])",
6
+ "textarea:not([disabled])",
7
+ "input:not([disabled])",
8
+ "select:not([disabled])",
9
+ '[tabindex]:not([tabindex="-1"])'
10
+ ].join(", ");
11
+ class FocusTrap extends Controller {
12
+ static values = {
13
+ active: { type: Boolean, default: true },
14
+ returnFocus: { type: Boolean, default: true },
15
+ initialFocus: { type: String, default: "" }
16
+ };
17
+ engaged = false;
18
+ previousFocus = null;
19
+ // Fires on connect for the initial value and on every subsequent change,
20
+ // so it is the single entry point for engaging/releasing the trap.
21
+ activeValueChanged(active) {
22
+ if (active)
23
+ this.engage();
24
+ else
25
+ this.release();
26
+ }
27
+ disconnect() {
28
+ this.release();
29
+ }
30
+ engage() {
31
+ if (this.engaged)
32
+ return;
33
+ this.engaged = true;
34
+ this.previousFocus = document.activeElement;
35
+ this.element.addEventListener("keydown", this.handleKeydown);
36
+ requestAnimationFrame(() => {
37
+ if (this.engaged)
38
+ this.focusInitial();
39
+ });
40
+ }
41
+ release() {
42
+ if (!this.engaged)
43
+ return;
44
+ this.engaged = false;
45
+ this.element.removeEventListener("keydown", this.handleKeydown);
46
+ if (this.returnFocusValue)
47
+ this.previousFocus?.focus();
48
+ this.previousFocus = null;
49
+ }
50
+ focusInitial() {
51
+ const target = this.initialFocusValue ? this.element.querySelector(this.initialFocusValue) : this.focusable()[0];
52
+ target?.focus();
53
+ }
54
+ focusable() {
55
+ return Array.from(
56
+ this.element.querySelectorAll(FOCUSABLE)
57
+ ).filter((el) => !el.closest("[inert]"));
58
+ }
59
+ handleKeydown = (event) => {
60
+ if (event.key !== "Tab")
61
+ return;
62
+ const focusable = this.focusable();
63
+ if (focusable.length === 0) {
64
+ event.preventDefault();
65
+ return;
66
+ }
67
+ const first = focusable[0];
68
+ const last = focusable[focusable.length - 1];
69
+ const active = document.activeElement;
70
+ if (event.shiftKey) {
71
+ if (active === first || !this.element.contains(active)) {
72
+ event.preventDefault();
73
+ last.focus();
74
+ }
75
+ } else if (active === last || !this.element.contains(active)) {
76
+ event.preventDefault();
77
+ first.focus();
78
+ }
79
+ };
80
+ }
81
+
82
+ export { FocusTrap as default };
83
+ //# sourceMappingURL=focus_trap_component_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focus_trap_component_controller.js","sources":["../../../../../../../app/components/midwest/focus_trap_component/focus_trap_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'textarea:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])'\n].join(', ')\n\n// Keeps keyboard focus cycling within the element while active.\n//\n// Values\n// active — whether the trap is engaged (toggle to open/close)\n// returnFocus — restore focus to the previously focused element on release\n// initialFocus — selector for the element to focus first (defaults to the\n// first focusable descendant)\nexport default class FocusTrap extends Controller<HTMLElement> {\n static values = {\n active: { type: Boolean, default: true },\n returnFocus: { type: Boolean, default: true },\n initialFocus: { type: String, default: '' }\n }\n\n declare activeValue: boolean\n declare returnFocusValue: boolean\n declare initialFocusValue: string\n\n private engaged = false\n private previousFocus: HTMLElement | null = null\n\n // Fires on connect for the initial value and on every subsequent change,\n // so it is the single entry point for engaging/releasing the trap.\n activeValueChanged (active: boolean) {\n if (active) this.engage()\n else this.release()\n }\n\n disconnect () {\n this.release()\n }\n\n private engage () {\n if (this.engaged) return\n this.engaged = true\n this.previousFocus = document.activeElement as HTMLElement | null\n this.element.addEventListener('keydown', this.handleKeydown)\n requestAnimationFrame(() => { if (this.engaged) this.focusInitial() })\n }\n\n private release () {\n if (!this.engaged) return\n this.engaged = false\n this.element.removeEventListener('keydown', this.handleKeydown)\n if (this.returnFocusValue) this.previousFocus?.focus()\n this.previousFocus = null\n }\n\n private focusInitial () {\n const target = this.initialFocusValue\n ? this.element.querySelector<HTMLElement>(this.initialFocusValue)\n : this.focusable()[0]\n target?.focus()\n }\n\n private focusable (): HTMLElement[] {\n return Array.from(\n this.element.querySelectorAll<HTMLElement>(FOCUSABLE)\n ).filter(el => !el.closest('[inert]'))\n }\n\n private handleKeydown = (event: KeyboardEvent) => {\n if (event.key !== 'Tab') return\n\n const focusable = this.focusable()\n\n // Nothing to focus — keep focus pinned to the trap itself.\n if (focusable.length === 0) {\n event.preventDefault()\n return\n }\n\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n const active = document.activeElement\n\n if (event.shiftKey) {\n if (active === first || !this.element.contains(active)) {\n event.preventDefault()\n last.focus()\n }\n } else if (active === last || !this.element.contains(active)) {\n event.preventDefault()\n first.focus()\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ class ThemeSwitch extends Controller {
4
+ static values = {
5
+ storageKey: { type: String, default: "midwest-theme" }
6
+ };
7
+ connect() {
8
+ const isDark = this.resolveTheme() === "dark";
9
+ this.apply(isDark);
10
+ this.element.checked = isDark;
11
+ }
12
+ toggle() {
13
+ const isDark = this.element.checked;
14
+ this.apply(isDark);
15
+ this.persist(isDark ? "dark" : "light");
16
+ }
17
+ resolveTheme() {
18
+ const stored = this.read();
19
+ if (stored === "dark" || stored === "light")
20
+ return stored;
21
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
22
+ }
23
+ apply(isDark) {
24
+ document.documentElement.classList.toggle("dark", isDark);
25
+ }
26
+ read() {
27
+ try {
28
+ return window.localStorage.getItem(this.storageKeyValue);
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ persist(theme) {
34
+ try {
35
+ window.localStorage.setItem(this.storageKeyValue, theme);
36
+ } catch {
37
+ }
38
+ }
39
+ }
40
+
41
+ export { ThemeSwitch as default };
42
+ //# sourceMappingURL=theme_switch_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme_switch_controller.js","sources":["../../../../../../../../app/components/midwest/form/switch_component/theme_switch_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Turns a switch into a dark/light theme toggle. On connect it resolves the\n// active theme from localStorage (falling back to the OS `prefers-color-scheme`\n// preference), applies the `dark` class to <html>, and reflects that state on\n// the checkbox. Toggling the switch flips the theme and persists the choice.\nexport default class ThemeSwitch extends Controller<HTMLInputElement> {\n static values = {\n storageKey: { type: String, default: 'midwest-theme' }\n }\n\n declare storageKeyValue: string\n\n connect () {\n const isDark = this.resolveTheme() === 'dark'\n this.apply(isDark)\n this.element.checked = isDark\n }\n\n toggle () {\n const isDark = this.element.checked\n this.apply(isDark)\n this.persist(isDark ? 'dark' : 'light')\n }\n\n private resolveTheme (): 'dark' | 'light' {\n const stored = this.read()\n if (stored === 'dark' || stored === 'light') return stored\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n }\n\n private apply (isDark: boolean) {\n document.documentElement.classList.toggle('dark', isDark)\n }\n\n private read (): string | null {\n try {\n return window.localStorage.getItem(this.storageKeyValue)\n } catch {\n return null\n }\n }\n\n private persist (theme: 'dark' | 'light') {\n try {\n window.localStorage.setItem(this.storageKeyValue, theme)\n } catch {\n // Storage may be unavailable (private mode, blocked cookies); toggling\n // still works for the current page, it just won't be remembered.\n }\n }\n}\n"],"names":[],"mappings":";;AAMA,MAAqB,oBAAoB,UAA6B,CAAA;AAAA,EACpE,OAAO,MAAS,GAAA;AAAA,IACd,UAAY,EAAA,EAAE,IAAM,EAAA,MAAA,EAAQ,SAAS,eAAgB,EAAA;AAAA,GACvD,CAAA;AAAA,EAIA,OAAW,GAAA;AACT,IAAM,MAAA,MAAA,GAAS,IAAK,CAAA,YAAA,EAAmB,KAAA,MAAA,CAAA;AACvC,IAAA,IAAA,CAAK,MAAM,MAAM,CAAA,CAAA;AACjB,IAAA,IAAA,CAAK,QAAQ,OAAU,GAAA,MAAA,CAAA;AAAA,GACzB;AAAA,EAEA,MAAU,GAAA;AACR,IAAM,MAAA,MAAA,GAAS,KAAK,OAAQ,CAAA,OAAA,CAAA;AAC5B,IAAA,IAAA,CAAK,MAAM,MAAM,CAAA,CAAA;AACjB,IAAK,IAAA,CAAA,OAAA,CAAQ,MAAS,GAAA,MAAA,GAAS,OAAO,CAAA,CAAA;AAAA,GACxC;AAAA,EAEQ,YAAkC,GAAA;AACxC,IAAM,MAAA,MAAA,GAAS,KAAK,IAAK,EAAA,CAAA;AACzB,IAAI,IAAA,MAAA,KAAW,UAAU,MAAW,KAAA,OAAA;AAAS,MAAO,OAAA,MAAA,CAAA;AACpD,IAAA,OAAO,MAAO,CAAA,UAAA,CAAW,8BAA8B,CAAA,CAAE,UAAU,MAAS,GAAA,OAAA,CAAA;AAAA,GAC9E;AAAA,EAEQ,MAAO,MAAiB,EAAA;AAC9B,IAAA,QAAA,CAAS,eAAgB,CAAA,SAAA,CAAU,MAAO,CAAA,MAAA,EAAQ,MAAM,CAAA,CAAA;AAAA,GAC1D;AAAA,EAEQ,IAAuB,GAAA;AAC7B,IAAI,IAAA;AACF,MAAA,OAAO,MAAO,CAAA,YAAA,CAAa,OAAQ,CAAA,IAAA,CAAK,eAAe,CAAA,CAAA;AAAA,KACvD,CAAA,MAAA;AACA,MAAO,OAAA,IAAA,CAAA;AAAA,KACT;AAAA,GACF;AAAA,EAEQ,QAAS,KAAyB,EAAA;AACxC,IAAI,IAAA;AACF,MAAA,MAAA,CAAO,YAAa,CAAA,OAAA,CAAQ,IAAK,CAAA,eAAA,EAAiB,KAAK,CAAA,CAAA;AAAA,KACvD,CAAA,MAAA;AAAA,KAGF;AAAA,GACF;AACF;;;;"}
@@ -0,0 +1,43 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ class ViewportObserver extends Controller {
4
+ static values = {
5
+ threshold: { type: Number, default: 0 },
6
+ rootMargin: { type: String, default: "0px" },
7
+ once: { type: Boolean, default: false },
8
+ visibleClass: { type: String, default: "" }
9
+ };
10
+ observer = null;
11
+ connect() {
12
+ this.observer = new IntersectionObserver(
13
+ (entries) => {
14
+ for (const entry of entries)
15
+ this.handle(entry);
16
+ },
17
+ { threshold: this.thresholdValue, rootMargin: this.rootMarginValue }
18
+ );
19
+ this.observer.observe(this.element);
20
+ }
21
+ disconnect() {
22
+ this.observer?.disconnect();
23
+ this.observer = null;
24
+ }
25
+ handle(entry) {
26
+ if (entry.isIntersecting) {
27
+ this.element.setAttribute("data-intersecting", "true");
28
+ if (this.visibleClassValue)
29
+ this.element.classList.add(this.visibleClassValue);
30
+ this.dispatch("enter", { detail: { entry }, bubbles: true });
31
+ if (this.onceValue)
32
+ this.disconnect();
33
+ } else {
34
+ this.element.setAttribute("data-intersecting", "false");
35
+ if (this.visibleClassValue)
36
+ this.element.classList.remove(this.visibleClassValue);
37
+ this.dispatch("leave", { detail: { entry }, bubbles: true });
38
+ }
39
+ }
40
+ }
41
+
42
+ export { ViewportObserver as default };
43
+ //# sourceMappingURL=intersection_observer_component_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intersection_observer_component_controller.js","sources":["../../../../../../../app/components/midwest/intersection_observer_component/intersection_observer_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Declarative viewport detection. Wraps the IntersectionObserver API so markup\n// can react to entering/leaving the viewport with no bespoke JavaScript:\n// - reflects state via a `data-intersecting` attribute (a CSS styling hook)\n// - toggles an optional `visibleClass` while the element is intersecting\n// - dispatches `midwest-intersection-observer:enter` / `:leave` events\n//\n// Values\n// threshold — fraction of the element that must be visible to trigger (0–1)\n// rootMargin — margin around the viewport root (CSS shorthand string)\n// once — stop observing after the first time it enters the viewport\n// visibleClass — class name toggled on the element while intersecting\nexport default class ViewportObserver extends Controller<HTMLElement> {\n static values = {\n threshold: { type: Number, default: 0 },\n rootMargin: { type: String, default: '0px' },\n once: { type: Boolean, default: false },\n visibleClass: { type: String, default: '' }\n }\n\n declare thresholdValue: number\n declare rootMarginValue: string\n declare onceValue: boolean\n declare visibleClassValue: string\n\n private observer: IntersectionObserver | null = null\n\n connect () {\n this.observer = new IntersectionObserver(\n (entries) => { for (const entry of entries) this.handle(entry) },\n { threshold: this.thresholdValue, rootMargin: this.rootMarginValue }\n )\n this.observer.observe(this.element)\n }\n\n disconnect () {\n this.observer?.disconnect()\n this.observer = null\n }\n\n private handle (entry: IntersectionObserverEntry) {\n if (entry.isIntersecting) {\n this.element.setAttribute('data-intersecting', 'true')\n if (this.visibleClassValue) this.element.classList.add(this.visibleClassValue)\n this.dispatch('enter', { detail: { entry }, bubbles: true })\n\n if (this.onceValue) this.disconnect()\n } else {\n this.element.setAttribute('data-intersecting', 'false')\n if (this.visibleClassValue) this.element.classList.remove(this.visibleClassValue)\n this.dispatch('leave', { detail: { entry }, bubbles: true })\n }\n }\n}\n"],"names":[],"mappings":";;AAaA,MAAqB,yBAAyB,UAAwB,CAAA;AAAA,EACpE,OAAO,MAAS,GAAA;AAAA,IACd,SAAW,EAAA,EAAE,IAAM,EAAA,MAAA,EAAQ,SAAS,CAAE,EAAA;AAAA,IACtC,UAAY,EAAA,EAAE,IAAM,EAAA,MAAA,EAAQ,SAAS,KAAM,EAAA;AAAA,IAC3C,IAAM,EAAA,EAAE,IAAM,EAAA,OAAA,EAAS,SAAS,KAAM,EAAA;AAAA,IACtC,YAAc,EAAA,EAAE,IAAM,EAAA,MAAA,EAAQ,SAAS,EAAG,EAAA;AAAA,GAC5C,CAAA;AAAA,EAOQ,QAAwC,GAAA,IAAA,CAAA;AAAA,EAEhD,OAAW,GAAA;AACT,IAAA,IAAA,CAAK,WAAW,IAAI,oBAAA;AAAA,MAClB,CAAC,OAAY,KAAA;AAAE,QAAA,KAAA,MAAW,KAAS,IAAA,OAAA;AAAS,UAAA,IAAA,CAAK,OAAO,KAAK,CAAA,CAAA;AAAA,OAAE;AAAA,MAC/D,EAAE,SAAW,EAAA,IAAA,CAAK,cAAgB,EAAA,UAAA,EAAY,KAAK,eAAgB,EAAA;AAAA,KACrE,CAAA;AACA,IAAK,IAAA,CAAA,QAAA,CAAS,OAAQ,CAAA,IAAA,CAAK,OAAO,CAAA,CAAA;AAAA,GACpC;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,UAAU,UAAW,EAAA,CAAA;AAC1B,IAAA,IAAA,CAAK,QAAW,GAAA,IAAA,CAAA;AAAA,GAClB;AAAA,EAEQ,OAAQ,KAAkC,EAAA;AAChD,IAAA,IAAI,MAAM,cAAgB,EAAA;AACxB,MAAK,IAAA,CAAA,OAAA,CAAQ,YAAa,CAAA,mBAAA,EAAqB,MAAM,CAAA,CAAA;AACrD,MAAA,IAAI,IAAK,CAAA,iBAAA;AAAmB,QAAA,IAAA,CAAK,OAAQ,CAAA,SAAA,CAAU,GAAI,CAAA,IAAA,CAAK,iBAAiB,CAAA,CAAA;AAC7E,MAAK,IAAA,CAAA,QAAA,CAAS,SAAS,EAAE,MAAA,EAAQ,EAAE,KAAM,EAAA,EAAG,OAAS,EAAA,IAAA,EAAM,CAAA,CAAA;AAE3D,MAAA,IAAI,IAAK,CAAA,SAAA;AAAW,QAAA,IAAA,CAAK,UAAW,EAAA,CAAA;AAAA,KAC/B,MAAA;AACL,MAAK,IAAA,CAAA,OAAA,CAAQ,YAAa,CAAA,mBAAA,EAAqB,OAAO,CAAA,CAAA;AACtD,MAAA,IAAI,IAAK,CAAA,iBAAA;AAAmB,QAAA,IAAA,CAAK,OAAQ,CAAA,SAAA,CAAU,MAAO,CAAA,IAAA,CAAK,iBAAiB,CAAA,CAAA;AAChF,MAAK,IAAA,CAAA,QAAA,CAAS,SAAS,EAAE,MAAA,EAAQ,EAAE,KAAM,EAAA,EAAG,OAAS,EAAA,IAAA,EAAM,CAAA,CAAA;AAAA,KAC7D;AAAA,GACF;AACF;;;;"}