@unabridged/midwest 0.24.4 → 0.26.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/app/assets/javascript/midwest/index.ts +6 -0
- package/app/assets/javascript/midwest.js +642 -24
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +13 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +6 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/assets_component/assets_component_controller.js +47 -0
- package/dist/javascript/collection/app/components/midwest/assets_component/assets_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/chart_component/chart_component_controller.js +122 -2
- package/dist/javascript/collection/app/components/midwest/chart_component/chart_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/context_menu_component/context_menu_component_controller.js +71 -0
- package/dist/javascript/collection/app/components/midwest/context_menu_component/context_menu_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/dialog_component/dialog_component_controller.js +33 -1
- package/dist/javascript/collection/app/components/midwest/dialog_component/dialog_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/form_controller.js +37 -3
- package/dist/javascript/collection/app/components/midwest/form/form_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/split_view_component/split_view_component_controller.js +99 -0
- package/dist/javascript/collection/app/components/midwest/split_view_component/split_view_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/table_component/table_component_controller.js +239 -15
- package/dist/javascript/collection/app/components/midwest/table_component/table_component_controller.js.map +1 -1
- package/dist/javascript/midwest.d.ts +16 -1
- package/dist/javascript/midwest.js +642 -24
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -43,6 +43,9 @@ import PageTransition from '../../../components/midwest/page_transition_componen
|
|
|
43
43
|
import Map from '../../../components/midwest/map_component/map_component_controller.js';
|
|
44
44
|
import Address from '../../../components/midwest/form/address_component/address_component_controller.js';
|
|
45
45
|
import Popover from '../../../components/midwest/popover_component/popover_component_controller.js';
|
|
46
|
+
import ContextMenu from '../../../components/midwest/context_menu_component/context_menu_component_controller.js';
|
|
47
|
+
import SplitView from '../../../components/midwest/split_view_component/split_view_component_controller.js';
|
|
48
|
+
import Assets from '../../../components/midwest/assets_component/assets_component_controller.js';
|
|
46
49
|
|
|
47
50
|
function registerMidwestControllers(application) {
|
|
48
51
|
application.register("midwest-card", Card);
|
|
@@ -90,6 +93,9 @@ function registerMidwestControllers(application) {
|
|
|
90
93
|
application.register("midwest-map", Map);
|
|
91
94
|
application.register("midwest-address", Address);
|
|
92
95
|
application.register("midwest-popover", Popover);
|
|
96
|
+
application.register("midwest-context-menu", ContextMenu);
|
|
97
|
+
application.register("midwest-split-view", SplitView);
|
|
98
|
+
application.register("midwest-assets", Assets);
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
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'\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'\nimport Panorama from '../../../components/midwest/panorama_component/panorama_component_controller'\nimport Playlist from '../../../components/midwest/playlist_component/playlist_component_controller'\nimport Motion from '../../../components/midwest/motion_component/motion_component_controller'\nimport PageTransition from '../../../components/midwest/page_transition_component/page_transition_component_controller'\nimport Map from '../../../components/midwest/map_component/map_component_controller'\nimport Address from '../../../components/midwest/form/address_component/address_component_controller'\nimport Popover from '../../../components/midwest/popover_component/popover_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 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 application.register('midwest-panorama', Panorama)\n application.register('midwest-playlist', Playlist)\n application.register('midwest-motion', Motion)\n application.register('midwest-page-transition', PageTransition)\n application.register('midwest-map', Map)\n application.register('midwest-address', Address)\n application.register('midwest-popover', Popover)\n /* EXPORTS */\n}\n"],"names":["Confirmation","IntersectionObserver"],"mappings":"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"}
|
|
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'\nimport Panorama from '../../../components/midwest/panorama_component/panorama_component_controller'\nimport Playlist from '../../../components/midwest/playlist_component/playlist_component_controller'\nimport Motion from '../../../components/midwest/motion_component/motion_component_controller'\nimport PageTransition from '../../../components/midwest/page_transition_component/page_transition_component_controller'\nimport Map from '../../../components/midwest/map_component/map_component_controller'\nimport Address from '../../../components/midwest/form/address_component/address_component_controller'\nimport Popover from '../../../components/midwest/popover_component/popover_component_controller'\nimport ContextMenu from '../../../components/midwest/context_menu_component/context_menu_component_controller'\nimport SplitView from '../../../components/midwest/split_view_component/split_view_component_controller'\nimport Assets from '../../../components/midwest/assets_component/assets_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 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 application.register('midwest-panorama', Panorama)\n application.register('midwest-playlist', Playlist)\n application.register('midwest-motion', Motion)\n application.register('midwest-page-transition', PageTransition)\n application.register('midwest-map', Map)\n application.register('midwest-address', Address)\n application.register('midwest-popover', Popover)\n application.register('midwest-context-menu', ContextMenu)\n application.register('midwest-split-view', SplitView)\n application.register('midwest-assets', Assets)\n /* EXPORTS */\n}\n"],"names":["Confirmation","IntersectionObserver"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
class Assets extends Controller {
|
|
4
|
+
static values = {
|
|
5
|
+
css: String,
|
|
6
|
+
js: String
|
|
7
|
+
};
|
|
8
|
+
connect() {
|
|
9
|
+
if (this.cssValue)
|
|
10
|
+
this.ensureStylesheet(this.cssValue);
|
|
11
|
+
if (this.jsValue)
|
|
12
|
+
this.ensureScript(this.jsValue);
|
|
13
|
+
}
|
|
14
|
+
ensureStylesheet(href) {
|
|
15
|
+
const url = this.absolute(href);
|
|
16
|
+
const present = Array.from(
|
|
17
|
+
document.querySelectorAll('link[rel="stylesheet"]')
|
|
18
|
+
).some((link2) => link2.href === url);
|
|
19
|
+
if (present)
|
|
20
|
+
return;
|
|
21
|
+
const link = document.createElement("link");
|
|
22
|
+
link.rel = "stylesheet";
|
|
23
|
+
link.href = href;
|
|
24
|
+
document.head.appendChild(link);
|
|
25
|
+
}
|
|
26
|
+
ensureScript(src) {
|
|
27
|
+
const url = this.absolute(src);
|
|
28
|
+
const present = Array.from(
|
|
29
|
+
document.querySelectorAll("script[src]")
|
|
30
|
+
).some((script2) => script2.src === url);
|
|
31
|
+
if (present)
|
|
32
|
+
return;
|
|
33
|
+
const script = document.createElement("script");
|
|
34
|
+
script.type = "module";
|
|
35
|
+
script.src = src;
|
|
36
|
+
document.head.appendChild(script);
|
|
37
|
+
}
|
|
38
|
+
// Resolve a possibly-relative URL to its absolute form for comparison.
|
|
39
|
+
absolute(url) {
|
|
40
|
+
const anchor = document.createElement("a");
|
|
41
|
+
anchor.href = url;
|
|
42
|
+
return anchor.href;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { Assets as default };
|
|
47
|
+
//# sourceMappingURL=assets_component_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets_component_controller.js","sources":["../../../../../../../app/components/midwest/assets_component/assets_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Injects Midwest's bundled stylesheet/script into <head> once, so a component\n// delivered via Turbo (Frame/Stream) into a page that does not already load\n// Midwest's assets still styles and behaves correctly. Idempotent: each URL is\n// compared, resolved to an absolute form, against what is already in the head.\nexport default class Assets extends Controller<HTMLElement> {\n static values = {\n css: String,\n js: String\n }\n\n declare cssValue: string\n declare jsValue: string\n\n connect (): void {\n if (this.cssValue) this.ensureStylesheet(this.cssValue)\n if (this.jsValue) this.ensureScript(this.jsValue)\n }\n\n private ensureStylesheet (href: string): void {\n const url = this.absolute(href)\n const present = Array.from(\n document.querySelectorAll<HTMLLinkElement>('link[rel=\"stylesheet\"]')\n ).some((link) => link.href === url)\n if (present) return\n\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = href\n document.head.appendChild(link)\n }\n\n private ensureScript (src: string): void {\n const url = this.absolute(src)\n const present = Array.from(\n document.querySelectorAll<HTMLScriptElement>('script[src]')\n ).some((script) => script.src === url)\n if (present) return\n\n const script = document.createElement('script')\n script.type = 'module'\n script.src = src\n document.head.appendChild(script)\n }\n\n // Resolve a possibly-relative URL to its absolute form for comparison.\n private absolute (url: string): string {\n const anchor = document.createElement('a')\n anchor.href = url\n return anchor.href\n }\n}\n"],"names":["link","script"],"mappings":";;AAMA,MAAqB,eAAe,UAAwB,CAAA;AAAA,EAC1D,OAAO,MAAS,GAAA;AAAA,IACd,GAAK,EAAA,MAAA;AAAA,IACL,EAAI,EAAA,MAAA;AAAA,GACN,CAAA;AAAA,EAKA,OAAiB,GAAA;AACf,IAAA,IAAI,IAAK,CAAA,QAAA;AAAU,MAAK,IAAA,CAAA,gBAAA,CAAiB,KAAK,QAAQ,CAAA,CAAA;AACtD,IAAA,IAAI,IAAK,CAAA,OAAA;AAAS,MAAK,IAAA,CAAA,YAAA,CAAa,KAAK,OAAO,CAAA,CAAA;AAAA,GAClD;AAAA,EAEQ,iBAAkB,IAAoB,EAAA;AAC5C,IAAM,MAAA,GAAA,GAAM,IAAK,CAAA,QAAA,CAAS,IAAI,CAAA,CAAA;AAC9B,IAAA,MAAM,UAAU,KAAM,CAAA,IAAA;AAAA,MACpB,QAAA,CAAS,iBAAkC,wBAAwB,CAAA;AAAA,MACnE,IAAK,CAAA,CAACA,KAASA,KAAAA,KAAAA,CAAK,SAAS,GAAG,CAAA,CAAA;AAClC,IAAI,IAAA,OAAA;AAAS,MAAA,OAAA;AAEb,IAAM,MAAA,IAAA,GAAO,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AAC1C,IAAA,IAAA,CAAK,GAAM,GAAA,YAAA,CAAA;AACX,IAAA,IAAA,CAAK,IAAO,GAAA,IAAA,CAAA;AACZ,IAAS,QAAA,CAAA,IAAA,CAAK,YAAY,IAAI,CAAA,CAAA;AAAA,GAChC;AAAA,EAEQ,aAAc,GAAmB,EAAA;AACvC,IAAM,MAAA,GAAA,GAAM,IAAK,CAAA,QAAA,CAAS,GAAG,CAAA,CAAA;AAC7B,IAAA,MAAM,UAAU,KAAM,CAAA,IAAA;AAAA,MACpB,QAAA,CAAS,iBAAoC,aAAa,CAAA;AAAA,MAC1D,IAAK,CAAA,CAACC,OAAWA,KAAAA,OAAAA,CAAO,QAAQ,GAAG,CAAA,CAAA;AACrC,IAAI,IAAA,OAAA;AAAS,MAAA,OAAA;AAEb,IAAM,MAAA,MAAA,GAAS,QAAS,CAAA,aAAA,CAAc,QAAQ,CAAA,CAAA;AAC9C,IAAA,MAAA,CAAO,IAAO,GAAA,QAAA,CAAA;AACd,IAAA,MAAA,CAAO,GAAM,GAAA,GAAA,CAAA;AACb,IAAS,QAAA,CAAA,IAAA,CAAK,YAAY,MAAM,CAAA,CAAA;AAAA,GAClC;AAAA;AAAA,EAGQ,SAAU,GAAqB,EAAA;AACrC,IAAM,MAAA,MAAA,GAAS,QAAS,CAAA,aAAA,CAAc,GAAG,CAAA,CAAA;AACzC,IAAA,MAAA,CAAO,IAAO,GAAA,GAAA,CAAA;AACd,IAAA,OAAO,MAAO,CAAA,IAAA,CAAA;AAAA,GAChB;AACF;;;;"}
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
class Chart extends Controller {
|
|
4
|
-
static targets = ["tooltip", "svg"];
|
|
5
|
-
static values = {
|
|
4
|
+
static targets = ["tooltip", "svg", "data"];
|
|
5
|
+
static values = {
|
|
6
|
+
tooltipEnabled: { type: Boolean, default: true },
|
|
7
|
+
filename: { type: String, default: "chart" }
|
|
8
|
+
};
|
|
9
|
+
// Presentation properties copied inline when serializing the SVG, so exported
|
|
10
|
+
// files render faithfully without the external component stylesheet.
|
|
11
|
+
static STYLE_PROPS = [
|
|
12
|
+
"fill",
|
|
13
|
+
"stroke",
|
|
14
|
+
"stroke-width",
|
|
15
|
+
"stroke-dasharray",
|
|
16
|
+
"stroke-linecap",
|
|
17
|
+
"stroke-linejoin",
|
|
18
|
+
"opacity",
|
|
19
|
+
"fill-opacity",
|
|
20
|
+
"stroke-opacity",
|
|
21
|
+
"font-family",
|
|
22
|
+
"font-size",
|
|
23
|
+
"font-weight",
|
|
24
|
+
"text-anchor",
|
|
25
|
+
"color"
|
|
26
|
+
];
|
|
6
27
|
connect() {
|
|
7
28
|
if (this.tooltipEnabledValue && this.hasTooltipTarget) {
|
|
8
29
|
this.attachListeners();
|
|
@@ -82,6 +103,105 @@ class Chart extends Controller {
|
|
|
82
103
|
this.tooltipTarget.style.left = `${left}px`;
|
|
83
104
|
this.tooltipTarget.style.top = `${top}px`;
|
|
84
105
|
}
|
|
106
|
+
// --- Export actions -------------------------------------------------------
|
|
107
|
+
// Download the chart as a standalone SVG file (styles inlined).
|
|
108
|
+
exportSvg() {
|
|
109
|
+
if (!this.hasSvgTarget)
|
|
110
|
+
return;
|
|
111
|
+
const blob = new Blob([this.serializeSvg()], { type: "image/svg+xml;charset=utf-8" });
|
|
112
|
+
this.downloadBlob(blob, `${this.filenameValue}.svg`);
|
|
113
|
+
}
|
|
114
|
+
// Rasterize the chart to a PNG via an offscreen canvas (2× for crisp output).
|
|
115
|
+
exportPng() {
|
|
116
|
+
if (!this.hasSvgTarget)
|
|
117
|
+
return;
|
|
118
|
+
const box = this.svgTarget.viewBox.baseVal;
|
|
119
|
+
const rect = this.svgTarget.getBoundingClientRect();
|
|
120
|
+
const width = Math.max(1, Math.round(rect.width || box.width));
|
|
121
|
+
const height = Math.max(1, Math.round(rect.height || box.height));
|
|
122
|
+
const scale = 2;
|
|
123
|
+
const blob = new Blob([this.serializeSvg()], { type: "image/svg+xml;charset=utf-8" });
|
|
124
|
+
const url = URL.createObjectURL(blob);
|
|
125
|
+
const img = new Image();
|
|
126
|
+
img.onload = () => {
|
|
127
|
+
const canvas = document.createElement("canvas");
|
|
128
|
+
canvas.width = width * scale;
|
|
129
|
+
canvas.height = height * scale;
|
|
130
|
+
const ctx = canvas.getContext("2d");
|
|
131
|
+
if (ctx == null) {
|
|
132
|
+
URL.revokeObjectURL(url);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
ctx.fillStyle = "#ffffff";
|
|
136
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
137
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
138
|
+
URL.revokeObjectURL(url);
|
|
139
|
+
canvas.toBlob((png) => {
|
|
140
|
+
if (png != null)
|
|
141
|
+
this.downloadBlob(png, `${this.filenameValue}.png`);
|
|
142
|
+
}, "image/png");
|
|
143
|
+
};
|
|
144
|
+
img.onerror = () => {
|
|
145
|
+
URL.revokeObjectURL(url);
|
|
146
|
+
};
|
|
147
|
+
img.src = url;
|
|
148
|
+
}
|
|
149
|
+
// Download the underlying data (chart.data_table) as a CSV file.
|
|
150
|
+
exportCsv() {
|
|
151
|
+
if (!this.hasDataTarget)
|
|
152
|
+
return;
|
|
153
|
+
const rows = JSON.parse(this.dataTarget.dataset.rows ?? "[]");
|
|
154
|
+
const csv = rows.map((row) => row.map((cell) => this.csvCell(cell)).join(",")).join("\r\n");
|
|
155
|
+
const blob = new Blob([`\uFEFF${csv}`], { type: "text/csv;charset=utf-8" });
|
|
156
|
+
this.downloadBlob(blob, `${this.filenameValue}.csv`);
|
|
157
|
+
}
|
|
158
|
+
csvCell(value) {
|
|
159
|
+
if (value == null)
|
|
160
|
+
return "";
|
|
161
|
+
const str = String(value);
|
|
162
|
+
return /["\n\r,]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str;
|
|
163
|
+
}
|
|
164
|
+
// Clone the live SVG, inline computed presentation styles onto every node, and
|
|
165
|
+
// serialize to a standalone XML string usable as a file or a canvas source.
|
|
166
|
+
serializeSvg() {
|
|
167
|
+
const clone = this.svgTarget.cloneNode(true);
|
|
168
|
+
this.inlineStyles(this.svgTarget, clone);
|
|
169
|
+
clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
170
|
+
clone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
171
|
+
const box = this.svgTarget.viewBox.baseVal;
|
|
172
|
+
if (box?.width) {
|
|
173
|
+
clone.setAttribute("width", String(box.width));
|
|
174
|
+
clone.setAttribute("height", String(box.height));
|
|
175
|
+
}
|
|
176
|
+
const xml = new XMLSerializer().serializeToString(clone);
|
|
177
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
178
|
+
${xml}`;
|
|
179
|
+
}
|
|
180
|
+
// Copy the whitelisted computed styles from each source node to the matching
|
|
181
|
+
// clone node. The trees are structurally identical, so index alignment holds.
|
|
182
|
+
inlineStyles(source, target) {
|
|
183
|
+
const sourceEls = [source, ...Array.from(source.querySelectorAll("*"))];
|
|
184
|
+
const targetEls = [target, ...Array.from(target.querySelectorAll("*"))];
|
|
185
|
+
sourceEls.forEach((el, i) => {
|
|
186
|
+
const computed = window.getComputedStyle(el);
|
|
187
|
+
const decls = Chart.STYLE_PROPS.map((prop) => {
|
|
188
|
+
const value = computed.getPropertyValue(prop);
|
|
189
|
+
return value ? `${prop}:${value}` : null;
|
|
190
|
+
}).filter((decl) => decl !== null);
|
|
191
|
+
if (decls.length > 0)
|
|
192
|
+
targetEls[i].setAttribute("style", decls.join(";"));
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
downloadBlob(blob, filename) {
|
|
196
|
+
const url = URL.createObjectURL(blob);
|
|
197
|
+
const link = document.createElement("a");
|
|
198
|
+
link.href = url;
|
|
199
|
+
link.download = filename;
|
|
200
|
+
document.body.appendChild(link);
|
|
201
|
+
link.click();
|
|
202
|
+
link.remove();
|
|
203
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
204
|
+
}
|
|
85
205
|
}
|
|
86
206
|
|
|
87
207
|
export { Chart as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart_component_controller.js","sources":["../../../../../../../app/components/midwest/chart_component/chart_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\nexport default class Chart extends Controller<HTMLElement> {\n static targets = ['tooltip', 'svg']\n static values = { tooltipEnabled: { type: Boolean, default: true } }\n\n declare readonly tooltipTarget: HTMLElement\n declare readonly hasTooltipTarget: boolean\n declare readonly svgTarget: SVGElement\n declare readonly hasSvgTarget: boolean\n declare tooltipEnabledValue: boolean\n\n connect (): void {\n if (this.tooltipEnabledValue && this.hasTooltipTarget) {\n this.attachListeners()\n }\n }\n\n disconnect (): void {\n if (!this.hasSvgTarget) return\n this.svgTarget.removeEventListener('pointerover', this.showTooltipOnPointer)\n this.svgTarget.removeEventListener('pointermove', this.moveTooltip)\n this.svgTarget.removeEventListener('pointerout', this.hideTooltip)\n this.svgTarget.removeEventListener('focusin', this.showTooltipOnFocus)\n this.svgTarget.removeEventListener('focusout', this.hideTooltip)\n }\n\n private attachListeners (): void {\n this.svgTarget.addEventListener('pointerover', this.showTooltipOnPointer)\n this.svgTarget.addEventListener('pointermove', this.moveTooltip)\n this.svgTarget.addEventListener('pointerout', this.hideTooltip)\n this.svgTarget.addEventListener('focusin', this.showTooltipOnFocus)\n this.svgTarget.addEventListener('focusout', this.hideTooltip)\n }\n\n // Arrow functions so `this` is stable for add/removeEventListener pairing.\n\n private showTooltipOnPointer = (event: PointerEvent): void => {\n if (!this.hasTooltipTarget) return\n const target = (event.target as SVGElement).closest('[data-tooltip-content]') as SVGElement | null\n if (!target) return\n this.tooltipTarget.textContent = target.dataset.tooltipContent ?? ''\n this.tooltipTarget.classList.add('is-visible')\n this.positionAtPointer(event)\n }\n\n // Reposition tooltip as the pointer moves within the SVG so it tracks the cursor.\n private moveTooltip = (event: PointerEvent): void => {\n if (!this.tooltipTarget.classList.contains('is-visible')) return\n this.positionAtPointer(event)\n }\n\n // Show and position the tooltip above the focused element for keyboard users.\n private showTooltipOnFocus = (event: FocusEvent): void => {\n if (!this.hasTooltipTarget) return\n const target = (event.target as SVGElement).closest('[data-tooltip-content]') as SVGElement | null\n if (!target) return\n this.tooltipTarget.textContent = target.dataset.tooltipContent ?? ''\n this.tooltipTarget.classList.add('is-visible')\n this.positionAtElement(target)\n }\n\n private hideTooltip = (): void => {\n if (!this.hasTooltipTarget) return\n this.tooltipTarget.classList.remove('is-visible')\n }\n\n // Position the tooltip relative to the pointer cursor.\n private positionAtPointer (event: PointerEvent): void {\n const rect = this.element.getBoundingClientRect()\n const tipW = this.tooltipTarget.offsetWidth\n const tipH = this.tooltipTarget.offsetHeight\n const rawLeft = event.clientX - rect.left + 8\n const rawTop = event.clientY - rect.top - 28\n const left = Math.min(rawLeft, rect.width - tipW - 4)\n const top = Math.max(rawTop, tipH + 4)\n this.tooltipTarget.style.left = `${left}px`\n this.tooltipTarget.style.top = `${top}px`\n }\n\n // Position the tooltip centred above a focused SVG element (keyboard navigation).\n private positionAtElement (el: SVGElement): void {\n const containerRect = this.element.getBoundingClientRect()\n const elRect = el.getBoundingClientRect()\n const tipW = this.tooltipTarget.offsetWidth\n const tipH = this.tooltipTarget.offsetHeight\n const rawLeft = elRect.left - containerRect.left + (elRect.width / 2) - (tipW / 2)\n const rawTop = elRect.top - containerRect.top - tipH - 8\n const left = Math.max(4, Math.min(rawLeft, containerRect.width - tipW - 4))\n const top = Math.max(4, rawTop)\n this.tooltipTarget.style.left = `${left}px`\n this.tooltipTarget.style.top = `${top}px`\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chart_component_controller.js","sources":["../../../../../../../app/components/midwest/chart_component/chart_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\nexport default class Chart extends Controller<HTMLElement> {\n static targets = ['tooltip', 'svg', 'data']\n static values = {\n tooltipEnabled: { type: Boolean, default: true },\n filename: { type: String, default: 'chart' }\n }\n\n declare readonly tooltipTarget: HTMLElement\n declare readonly hasTooltipTarget: boolean\n declare readonly svgTarget: SVGSVGElement\n declare readonly hasSvgTarget: boolean\n declare readonly dataTarget: HTMLElement\n declare readonly hasDataTarget: boolean\n declare tooltipEnabledValue: boolean\n declare filenameValue: string\n\n // Presentation properties copied inline when serializing the SVG, so exported\n // files render faithfully without the external component stylesheet.\n private static readonly STYLE_PROPS = [\n 'fill', 'stroke', 'stroke-width', 'stroke-dasharray', 'stroke-linecap',\n 'stroke-linejoin', 'opacity', 'fill-opacity', 'stroke-opacity',\n 'font-family', 'font-size', 'font-weight', 'text-anchor', 'color'\n ]\n\n connect (): void {\n if (this.tooltipEnabledValue && this.hasTooltipTarget) {\n this.attachListeners()\n }\n }\n\n disconnect (): void {\n if (!this.hasSvgTarget) return\n this.svgTarget.removeEventListener('pointerover', this.showTooltipOnPointer)\n this.svgTarget.removeEventListener('pointermove', this.moveTooltip)\n this.svgTarget.removeEventListener('pointerout', this.hideTooltip)\n this.svgTarget.removeEventListener('focusin', this.showTooltipOnFocus)\n this.svgTarget.removeEventListener('focusout', this.hideTooltip)\n }\n\n private attachListeners (): void {\n this.svgTarget.addEventListener('pointerover', this.showTooltipOnPointer)\n this.svgTarget.addEventListener('pointermove', this.moveTooltip)\n this.svgTarget.addEventListener('pointerout', this.hideTooltip)\n this.svgTarget.addEventListener('focusin', this.showTooltipOnFocus)\n this.svgTarget.addEventListener('focusout', this.hideTooltip)\n }\n\n // Arrow functions so `this` is stable for add/removeEventListener pairing.\n\n private showTooltipOnPointer = (event: PointerEvent): void => {\n if (!this.hasTooltipTarget) return\n const target = (event.target as SVGElement).closest('[data-tooltip-content]') as SVGElement | null\n if (!target) return\n this.tooltipTarget.textContent = target.dataset.tooltipContent ?? ''\n this.tooltipTarget.classList.add('is-visible')\n this.positionAtPointer(event)\n }\n\n // Reposition tooltip as the pointer moves within the SVG so it tracks the cursor.\n private moveTooltip = (event: PointerEvent): void => {\n if (!this.tooltipTarget.classList.contains('is-visible')) return\n this.positionAtPointer(event)\n }\n\n // Show and position the tooltip above the focused element for keyboard users.\n private showTooltipOnFocus = (event: FocusEvent): void => {\n if (!this.hasTooltipTarget) return\n const target = (event.target as SVGElement).closest('[data-tooltip-content]') as SVGElement | null\n if (!target) return\n this.tooltipTarget.textContent = target.dataset.tooltipContent ?? ''\n this.tooltipTarget.classList.add('is-visible')\n this.positionAtElement(target)\n }\n\n private hideTooltip = (): void => {\n if (!this.hasTooltipTarget) return\n this.tooltipTarget.classList.remove('is-visible')\n }\n\n // Position the tooltip relative to the pointer cursor.\n private positionAtPointer (event: PointerEvent): void {\n const rect = this.element.getBoundingClientRect()\n const tipW = this.tooltipTarget.offsetWidth\n const tipH = this.tooltipTarget.offsetHeight\n const rawLeft = event.clientX - rect.left + 8\n const rawTop = event.clientY - rect.top - 28\n const left = Math.min(rawLeft, rect.width - tipW - 4)\n const top = Math.max(rawTop, tipH + 4)\n this.tooltipTarget.style.left = `${left}px`\n this.tooltipTarget.style.top = `${top}px`\n }\n\n // Position the tooltip centred above a focused SVG element (keyboard navigation).\n private positionAtElement (el: SVGElement): void {\n const containerRect = this.element.getBoundingClientRect()\n const elRect = el.getBoundingClientRect()\n const tipW = this.tooltipTarget.offsetWidth\n const tipH = this.tooltipTarget.offsetHeight\n const rawLeft = elRect.left - containerRect.left + (elRect.width / 2) - (tipW / 2)\n const rawTop = elRect.top - containerRect.top - tipH - 8\n const left = Math.max(4, Math.min(rawLeft, containerRect.width - tipW - 4))\n const top = Math.max(4, rawTop)\n this.tooltipTarget.style.left = `${left}px`\n this.tooltipTarget.style.top = `${top}px`\n }\n\n // --- Export actions -------------------------------------------------------\n\n // Download the chart as a standalone SVG file (styles inlined).\n exportSvg (): void {\n if (!this.hasSvgTarget) return\n const blob = new Blob([this.serializeSvg()], { type: 'image/svg+xml;charset=utf-8' })\n this.downloadBlob(blob, `${this.filenameValue}.svg`)\n }\n\n // Rasterize the chart to a PNG via an offscreen canvas (2× for crisp output).\n exportPng (): void {\n if (!this.hasSvgTarget) return\n const box = this.svgTarget.viewBox.baseVal\n const rect = this.svgTarget.getBoundingClientRect()\n const width = Math.max(1, Math.round(rect.width || box.width))\n const height = Math.max(1, Math.round(rect.height || box.height))\n const scale = 2\n\n const blob = new Blob([this.serializeSvg()], { type: 'image/svg+xml;charset=utf-8' })\n const url = URL.createObjectURL(blob)\n const img = new Image()\n\n img.onload = (): void => {\n const canvas = document.createElement('canvas')\n canvas.width = width * scale\n canvas.height = height * scale\n const ctx = canvas.getContext('2d')\n if (ctx == null) { URL.revokeObjectURL(url); return }\n // Opaque white background — PNG has no page behind it like the SVG does.\n ctx.fillStyle = '#ffffff'\n ctx.fillRect(0, 0, canvas.width, canvas.height)\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\n URL.revokeObjectURL(url)\n canvas.toBlob((png) => {\n if (png != null) this.downloadBlob(png, `${this.filenameValue}.png`)\n }, 'image/png')\n }\n img.onerror = (): void => { URL.revokeObjectURL(url) }\n img.src = url\n }\n\n // Download the underlying data (chart.data_table) as a CSV file.\n exportCsv (): void {\n if (!this.hasDataTarget) return\n const rows = JSON.parse(this.dataTarget.dataset.rows ?? '[]') as (string | number | null)[][]\n const csv = rows.map((row) => row.map((cell) => this.csvCell(cell)).join(',')).join('\\r\\n')\n // Prepend a UTF-8 BOM so Excel opens accented characters correctly.\n const blob = new Blob([`\\uFEFF${csv}`], { type: 'text/csv;charset=utf-8' })\n this.downloadBlob(blob, `${this.filenameValue}.csv`)\n }\n\n private csvCell (value: string | number | null): string {\n if (value == null) return ''\n const str = String(value)\n return /[\"\\n\\r,]/.test(str) ? `\"${str.replace(/\"/g, '\"\"')}\"` : str\n }\n\n // Clone the live SVG, inline computed presentation styles onto every node, and\n // serialize to a standalone XML string usable as a file or a canvas source.\n private serializeSvg (): string {\n const clone = this.svgTarget.cloneNode(true) as SVGElement\n this.inlineStyles(this.svgTarget, clone)\n\n clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg')\n clone.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink')\n const box = this.svgTarget.viewBox.baseVal\n if (box?.width) {\n clone.setAttribute('width', String(box.width))\n clone.setAttribute('height', String(box.height))\n }\n\n const xml = new XMLSerializer().serializeToString(clone)\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n${xml}`\n }\n\n // Copy the whitelisted computed styles from each source node to the matching\n // clone node. The trees are structurally identical, so index alignment holds.\n private inlineStyles (source: Element, target: Element): void {\n const sourceEls: Element[] = [source, ...Array.from(source.querySelectorAll('*'))]\n const targetEls: Element[] = [target, ...Array.from(target.querySelectorAll('*'))]\n\n sourceEls.forEach((el, i) => {\n const computed = window.getComputedStyle(el)\n const decls = Chart.STYLE_PROPS\n .map((prop) => {\n const value = computed.getPropertyValue(prop)\n return value ? `${prop}:${value}` : null\n })\n .filter((decl): decl is string => decl !== null)\n if (decls.length > 0) targetEls[i].setAttribute('style', decls.join(';'))\n })\n }\n\n private downloadBlob (blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.href = url\n link.download = filename\n document.body.appendChild(link)\n link.click()\n link.remove()\n setTimeout(() => URL.revokeObjectURL(url), 1000)\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
class ContextMenu extends Controller {
|
|
4
|
+
static targets = ["menu"];
|
|
5
|
+
boundOutside = (event) => this.onOutside(event);
|
|
6
|
+
boundKeydown = (event) => this.onKeydown(event);
|
|
7
|
+
boundDismiss = () => this.close();
|
|
8
|
+
open(event) {
|
|
9
|
+
event.preventDefault();
|
|
10
|
+
this.show(event.clientX, event.clientY);
|
|
11
|
+
}
|
|
12
|
+
show(x, y) {
|
|
13
|
+
const menu = this.menuTarget;
|
|
14
|
+
if (menu.matches(":popover-open"))
|
|
15
|
+
menu.hidePopover();
|
|
16
|
+
menu.style.left = "0px";
|
|
17
|
+
menu.style.top = "0px";
|
|
18
|
+
menu.showPopover();
|
|
19
|
+
const rect = menu.getBoundingClientRect();
|
|
20
|
+
const vw = document.documentElement.clientWidth;
|
|
21
|
+
const vh = document.documentElement.clientHeight;
|
|
22
|
+
const left = Math.min(x, Math.max(0, vw - rect.width - 4));
|
|
23
|
+
const top = Math.min(y, Math.max(0, vh - rect.height - 4));
|
|
24
|
+
menu.style.left = `${left}px`;
|
|
25
|
+
menu.style.top = `${top}px`;
|
|
26
|
+
this.addListeners();
|
|
27
|
+
}
|
|
28
|
+
close() {
|
|
29
|
+
if (this.menuTarget.matches(":popover-open"))
|
|
30
|
+
this.menuTarget.hidePopover();
|
|
31
|
+
this.removeListeners();
|
|
32
|
+
}
|
|
33
|
+
// Delegated from the menu: activating a leaf item closes the menu, but a click
|
|
34
|
+
// that opens a sub-surface must keep it open. Both a submenu parent
|
|
35
|
+
// (`as: :nested`) and a confirmation trigger open a popover via
|
|
36
|
+
// `popovertarget`, so any such trigger is left alone — the confirmation dialog
|
|
37
|
+
// (or submenu) then lives on inside the still-open menu.
|
|
38
|
+
select(event) {
|
|
39
|
+
const target = event.target;
|
|
40
|
+
if (target.closest("[popovertarget]"))
|
|
41
|
+
return;
|
|
42
|
+
if (target.closest("a.midwest-dropdown-item, button.midwest-dropdown-item"))
|
|
43
|
+
this.close();
|
|
44
|
+
}
|
|
45
|
+
onOutside(event) {
|
|
46
|
+
if (!this.menuTarget.contains(event.target))
|
|
47
|
+
this.close();
|
|
48
|
+
}
|
|
49
|
+
onKeydown(event) {
|
|
50
|
+
if (event.key === "Escape")
|
|
51
|
+
this.close();
|
|
52
|
+
}
|
|
53
|
+
addListeners() {
|
|
54
|
+
document.addEventListener("pointerdown", this.boundOutside, true);
|
|
55
|
+
document.addEventListener("keydown", this.boundKeydown, true);
|
|
56
|
+
window.addEventListener("scroll", this.boundDismiss, true);
|
|
57
|
+
window.addEventListener("resize", this.boundDismiss);
|
|
58
|
+
}
|
|
59
|
+
removeListeners() {
|
|
60
|
+
document.removeEventListener("pointerdown", this.boundOutside, true);
|
|
61
|
+
document.removeEventListener("keydown", this.boundKeydown, true);
|
|
62
|
+
window.removeEventListener("scroll", this.boundDismiss, true);
|
|
63
|
+
window.removeEventListener("resize", this.boundDismiss);
|
|
64
|
+
}
|
|
65
|
+
disconnect() {
|
|
66
|
+
this.removeListeners();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { ContextMenu as default };
|
|
71
|
+
//# sourceMappingURL=context_menu_component_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context_menu_component_controller.js","sources":["../../../../../../../app/components/midwest/context_menu_component/context_menu_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Right-click context menu. Opens the shared dropdown-menu shell as a top-layer\n// popover at the cursor, clamps it to the viewport, and dismisses on Escape,\n// outside pointerdown, scroll, or resize. Nested submenus, tooltips, and\n// confirmations are handled by the DropdownItemComponents inside it.\nexport default class ContextMenu extends Controller<HTMLElement> {\n static targets = ['menu']\n\n declare readonly menuTarget: HTMLElement\n\n private boundOutside = (event: Event) => this.onOutside(event)\n private boundKeydown = (event: KeyboardEvent) => this.onKeydown(event)\n private boundDismiss = () => this.close()\n\n open(event: MouseEvent) {\n event.preventDefault()\n this.show(event.clientX, event.clientY)\n }\n\n show(x: number, y: number) {\n const menu = this.menuTarget\n if (menu.matches(':popover-open')) menu.hidePopover()\n\n // Reveal first so it has measurable dimensions, then clamp to the viewport.\n menu.style.left = '0px'\n menu.style.top = '0px'\n menu.showPopover()\n\n const rect = menu.getBoundingClientRect()\n const vw = document.documentElement.clientWidth\n const vh = document.documentElement.clientHeight\n const left = Math.min(x, Math.max(0, vw - rect.width - 4))\n const top = Math.min(y, Math.max(0, vh - rect.height - 4))\n menu.style.left = `${left}px`\n menu.style.top = `${top}px`\n\n this.addListeners()\n }\n\n close() {\n if (this.menuTarget.matches(':popover-open')) this.menuTarget.hidePopover()\n this.removeListeners()\n }\n\n // Delegated from the menu: activating a leaf item closes the menu, but a click\n // that opens a sub-surface must keep it open. Both a submenu parent\n // (`as: :nested`) and a confirmation trigger open a popover via\n // `popovertarget`, so any such trigger is left alone — the confirmation dialog\n // (or submenu) then lives on inside the still-open menu.\n select(event: Event) {\n const target = event.target as HTMLElement\n if (target.closest('[popovertarget]')) return\n if (target.closest('a.midwest-dropdown-item, button.midwest-dropdown-item')) this.close()\n }\n\n private onOutside(event: Event) {\n if (!this.menuTarget.contains(event.target as Node)) this.close()\n }\n\n private onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') this.close()\n }\n\n private addListeners() {\n document.addEventListener('pointerdown', this.boundOutside, true)\n document.addEventListener('keydown', this.boundKeydown, true)\n window.addEventListener('scroll', this.boundDismiss, true)\n window.addEventListener('resize', this.boundDismiss)\n }\n\n private removeListeners() {\n document.removeEventListener('pointerdown', this.boundOutside, true)\n document.removeEventListener('keydown', this.boundKeydown, true)\n window.removeEventListener('scroll', this.boundDismiss, true)\n window.removeEventListener('resize', this.boundDismiss)\n }\n\n disconnect() {\n this.removeListeners()\n }\n}\n"],"names":[],"mappings":";;AAMA,MAAqB,oBAAoB,UAAwB,CAAA;AAAA,EAC/D,OAAO,OAAU,GAAA,CAAC,MAAM,CAAA,CAAA;AAAA,EAIhB,YAAe,GAAA,CAAC,KAAiB,KAAA,IAAA,CAAK,UAAU,KAAK,CAAA,CAAA;AAAA,EACrD,YAAe,GAAA,CAAC,KAAyB,KAAA,IAAA,CAAK,UAAU,KAAK,CAAA,CAAA;AAAA,EAC7D,YAAA,GAAe,MAAM,IAAA,CAAK,KAAM,EAAA,CAAA;AAAA,EAExC,KAAK,KAAmB,EAAA;AACtB,IAAA,KAAA,CAAM,cAAe,EAAA,CAAA;AACrB,IAAA,IAAA,CAAK,IAAK,CAAA,KAAA,CAAM,OAAS,EAAA,KAAA,CAAM,OAAO,CAAA,CAAA;AAAA,GACxC;AAAA,EAEA,IAAA,CAAK,GAAW,CAAW,EAAA;AACzB,IAAA,MAAM,OAAO,IAAK,CAAA,UAAA,CAAA;AAClB,IAAI,IAAA,IAAA,CAAK,QAAQ,eAAe,CAAA;AAAG,MAAA,IAAA,CAAK,WAAY,EAAA,CAAA;AAGpD,IAAA,IAAA,CAAK,MAAM,IAAO,GAAA,KAAA,CAAA;AAClB,IAAA,IAAA,CAAK,MAAM,GAAM,GAAA,KAAA,CAAA;AACjB,IAAA,IAAA,CAAK,WAAY,EAAA,CAAA;AAEjB,IAAM,MAAA,IAAA,GAAO,KAAK,qBAAsB,EAAA,CAAA;AACxC,IAAM,MAAA,EAAA,GAAK,SAAS,eAAgB,CAAA,WAAA,CAAA;AACpC,IAAM,MAAA,EAAA,GAAK,SAAS,eAAgB,CAAA,YAAA,CAAA;AACpC,IAAM,MAAA,IAAA,GAAO,IAAK,CAAA,GAAA,CAAI,CAAG,EAAA,IAAA,CAAK,GAAI,CAAA,CAAA,EAAG,EAAK,GAAA,IAAA,CAAK,KAAQ,GAAA,CAAC,CAAC,CAAA,CAAA;AACzD,IAAM,MAAA,GAAA,GAAM,IAAK,CAAA,GAAA,CAAI,CAAG,EAAA,IAAA,CAAK,GAAI,CAAA,CAAA,EAAG,EAAK,GAAA,IAAA,CAAK,MAAS,GAAA,CAAC,CAAC,CAAA,CAAA;AACzD,IAAK,IAAA,CAAA,KAAA,CAAM,OAAO,CAAG,EAAA,IAAA,CAAA,EAAA,CAAA,CAAA;AACrB,IAAK,IAAA,CAAA,KAAA,CAAM,MAAM,CAAG,EAAA,GAAA,CAAA,EAAA,CAAA,CAAA;AAEpB,IAAA,IAAA,CAAK,YAAa,EAAA,CAAA;AAAA,GACpB;AAAA,EAEA,KAAQ,GAAA;AACN,IAAI,IAAA,IAAA,CAAK,UAAW,CAAA,OAAA,CAAQ,eAAe,CAAA;AAAG,MAAA,IAAA,CAAK,WAAW,WAAY,EAAA,CAAA;AAC1E,IAAA,IAAA,CAAK,eAAgB,EAAA,CAAA;AAAA,GACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,KAAc,EAAA;AACnB,IAAA,MAAM,SAAS,KAAM,CAAA,MAAA,CAAA;AACrB,IAAI,IAAA,MAAA,CAAO,QAAQ,iBAAiB,CAAA;AAAG,MAAA,OAAA;AACvC,IAAI,IAAA,MAAA,CAAO,QAAQ,uDAAuD,CAAA;AAAG,MAAA,IAAA,CAAK,KAAM,EAAA,CAAA;AAAA,GAC1F;AAAA,EAEQ,UAAU,KAAc,EAAA;AAC9B,IAAA,IAAI,CAAC,IAAA,CAAK,UAAW,CAAA,QAAA,CAAS,MAAM,MAAc,CAAA;AAAG,MAAA,IAAA,CAAK,KAAM,EAAA,CAAA;AAAA,GAClE;AAAA,EAEQ,UAAU,KAAsB,EAAA;AACtC,IAAA,IAAI,MAAM,GAAQ,KAAA,QAAA;AAAU,MAAA,IAAA,CAAK,KAAM,EAAA,CAAA;AAAA,GACzC;AAAA,EAEQ,YAAe,GAAA;AACrB,IAAA,QAAA,CAAS,gBAAiB,CAAA,aAAA,EAAe,IAAK,CAAA,YAAA,EAAc,IAAI,CAAA,CAAA;AAChE,IAAA,QAAA,CAAS,gBAAiB,CAAA,SAAA,EAAW,IAAK,CAAA,YAAA,EAAc,IAAI,CAAA,CAAA;AAC5D,IAAA,MAAA,CAAO,gBAAiB,CAAA,QAAA,EAAU,IAAK,CAAA,YAAA,EAAc,IAAI,CAAA,CAAA;AACzD,IAAO,MAAA,CAAA,gBAAA,CAAiB,QAAU,EAAA,IAAA,CAAK,YAAY,CAAA,CAAA;AAAA,GACrD;AAAA,EAEQ,eAAkB,GAAA;AACxB,IAAA,QAAA,CAAS,mBAAoB,CAAA,aAAA,EAAe,IAAK,CAAA,YAAA,EAAc,IAAI,CAAA,CAAA;AACnE,IAAA,QAAA,CAAS,mBAAoB,CAAA,SAAA,EAAW,IAAK,CAAA,YAAA,EAAc,IAAI,CAAA,CAAA;AAC/D,IAAA,MAAA,CAAO,mBAAoB,CAAA,QAAA,EAAU,IAAK,CAAA,YAAA,EAAc,IAAI,CAAA,CAAA;AAC5D,IAAO,MAAA,CAAA,mBAAA,CAAoB,QAAU,EAAA,IAAA,CAAK,YAAY,CAAA,CAAA;AAAA,GACxD;AAAA,EAEA,UAAa,GAAA;AACX,IAAA,IAAA,CAAK,eAAgB,EAAA,CAAA;AAAA,GACvB;AACF;;;;"}
|
|
@@ -3,13 +3,18 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
3
3
|
class Dialog extends Controller {
|
|
4
4
|
static targets = ["remoteContent", "clearableForm"];
|
|
5
5
|
static values = {
|
|
6
|
-
resetFrame: { type: Boolean, default: false }
|
|
6
|
+
resetFrame: { type: Boolean, default: false },
|
|
7
|
+
history: { type: Boolean, default: false }
|
|
7
8
|
};
|
|
8
9
|
_previousFocus = null;
|
|
10
|
+
// True while a close is being driven by a history navigation (Back button),
|
|
11
|
+
// so onClose knows not to push another history entry in response.
|
|
12
|
+
_closingViaHistory = false;
|
|
9
13
|
connect() {
|
|
10
14
|
document.addEventListener("turbo:frame-load", this.bindToggleEls);
|
|
11
15
|
document.addEventListener("turbo:before-visit", this.dismiss);
|
|
12
16
|
document.addEventListener("click", this.handleClickOutside);
|
|
17
|
+
window.addEventListener("popstate", this.handlePopState);
|
|
13
18
|
this.dialog.addEventListener("close", this.onClose);
|
|
14
19
|
this.bindToggleEls();
|
|
15
20
|
}
|
|
@@ -17,8 +22,18 @@ class Dialog extends Controller {
|
|
|
17
22
|
document.removeEventListener("turbo:frame-load", this.bindToggleEls);
|
|
18
23
|
document.removeEventListener("turbo:before-visit", this.dismiss);
|
|
19
24
|
document.removeEventListener("click", this.handleClickOutside);
|
|
25
|
+
window.removeEventListener("popstate", this.handlePopState);
|
|
20
26
|
this.dialog.removeEventListener("close", this.onClose);
|
|
21
27
|
}
|
|
28
|
+
// History-aware modals: a Back navigation (popstate) closes the open dialog.
|
|
29
|
+
// The flag tells onClose this close came from history, so it must not push
|
|
30
|
+
// another entry back onto the stack.
|
|
31
|
+
handlePopState = () => {
|
|
32
|
+
if (!this.historyValue || !this.dialog.open)
|
|
33
|
+
return;
|
|
34
|
+
this._closingViaHistory = true;
|
|
35
|
+
this.dialog.close();
|
|
36
|
+
};
|
|
22
37
|
get dialog() {
|
|
23
38
|
return this.element;
|
|
24
39
|
}
|
|
@@ -70,6 +85,8 @@ class Dialog extends Controller {
|
|
|
70
85
|
if (!detail.success)
|
|
71
86
|
return;
|
|
72
87
|
const redirectUrl = detail.fetchResponse?.location?.href;
|
|
88
|
+
if (redirectUrl)
|
|
89
|
+
this._closingViaHistory = true;
|
|
73
90
|
this.dismiss();
|
|
74
91
|
if (redirectUrl)
|
|
75
92
|
window.Turbo?.visit(redirectUrl);
|
|
@@ -111,10 +128,25 @@ class Dialog extends Controller {
|
|
|
111
128
|
this._previousFocus?.focus();
|
|
112
129
|
this._previousFocus = null;
|
|
113
130
|
this.htmlEl.classList.toggle("overflow-hidden", false);
|
|
131
|
+
this.restoreHistory();
|
|
114
132
|
await this.delay(500);
|
|
115
133
|
this.resetForm();
|
|
116
134
|
this.resetFrame();
|
|
117
135
|
};
|
|
136
|
+
// When a history-aware modal is closed manually (Escape, backdrop, close
|
|
137
|
+
// button), pop the modal's advanced URL off the stack so the address bar
|
|
138
|
+
// returns to the page behind it. A close driven by the Back button already
|
|
139
|
+
// moved history, so it is skipped.
|
|
140
|
+
restoreHistory() {
|
|
141
|
+
if (!this.historyValue)
|
|
142
|
+
return;
|
|
143
|
+
if (this._closingViaHistory) {
|
|
144
|
+
this._closingViaHistory = false;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (window.history.length > 1)
|
|
148
|
+
window.history.back();
|
|
149
|
+
}
|
|
118
150
|
resetFrame() {
|
|
119
151
|
if (!this.resetFrameValue || this.frameEl == null)
|
|
120
152
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog_component_controller.js","sources":["../../../../../../../app/components/midwest/dialog_component/dialog_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\ndeclare global {\n interface Window {\n Turbo?: { visit(url: string): void }\n }\n}\n\nexport default class Dialog extends Controller<HTMLFormElement> {\n static targets = ['remoteContent', 'clearableForm']\n\n declare readonly hasRemoteContentTarget: boolean\n declare readonly remoteContentTarget: HTMLInputElement\n declare readonly remoteContentTargets: HTMLInputElement[]\n\n declare readonly hasClearableFormTarget: boolean\n declare readonly clearableFormTarget: HTMLFormElement\n declare readonly clearableFormTargets: HTMLFormElement[]\n\n static values = {\n resetFrame: { type: Boolean, default: false }\n }\n\n declare resetFrameValue: boolean\n declare readonly hasResetFrameValue: boolean\n\n private _previousFocus: HTMLElement | null = null\n\n connect () {\n document.addEventListener('turbo:frame-load', this.bindToggleEls)\n document.addEventListener('turbo:before-visit', this.dismiss)\n document.addEventListener('click', this.handleClickOutside)\n this.dialog.addEventListener('close', this.onClose)\n this.bindToggleEls()\n }\n\n disconnect () {\n document.removeEventListener('turbo:frame-load', this.bindToggleEls)\n document.removeEventListener('turbo:before-visit', this.dismiss)\n document.removeEventListener('click', this.handleClickOutside)\n this.dialog.removeEventListener('close', this.onClose)\n }\n\n get dialog () {\n return this.element\n }\n\n get htmlEl () {\n return document.documentElement\n }\n\n get toggleEls () {\n return document.querySelectorAll(`[data-toggle-dialog=\"${this.dialog.id}\"]`)\n }\n\n get frameEl () {\n return this.dialog.querySelector('turbo-frame')\n }\n\n // Arrow function so `this` is stable for add/removeEventListener pairing.\n private handleClickOutside = (event: Event) => {\n if (event.target !== this.dialog) return\n this.dismiss()\n }\n\n // Arrow function so `this` is stable for add/removeEventListener pairing.\n private bindToggleEls = () => {\n this.toggleEls.forEach((el) => {\n el.addEventListener('click', this.toggle.bind(this))\n })\n }\n\n remoteContentTargetConnected () {\n this.show()\n }\n\n toggle (e: any = undefined) {\n // if clicked elem is a link, prevent default behavior.\n if (e instanceof Event) e.preventDefault()\n this.htmlEl.classList.toggle('overflow-hidden', true)\n\n if (this.dialog.open) {\n this.dialog.close()\n } else {\n this._previousFocus = document.activeElement as HTMLElement\n this.dialog.showModal()\n this.dialog.addEventListener('keydown', this.handleFocusTrap)\n }\n }\n\n show (e: any = undefined) {\n // if clicked elem is a link, prevent default behavior.\n if (e instanceof Event) e.preventDefault()\n this.htmlEl.classList.toggle('overflow-hidden', true)\n this._previousFocus = document.activeElement as HTMLElement\n this.dialog.showModal()\n this.dialog.addEventListener('keydown', this.handleFocusTrap)\n }\n\n hide ({ detail }: { detail: { success: boolean; fetchResponse?: { location: URL } } }) {\n if (!detail.success) return\n const redirectUrl = detail.fetchResponse?.location?.href\n this.dismiss()\n if (redirectUrl) window.Turbo?.visit(redirectUrl)\n }\n\n // Arrow function so `this` is stable for add/removeEventListener pairing.\n dismiss = () => {\n this.dialog.close()\n }\n\n reloadPage () {\n window.location.reload()\n }\n\n private handleFocusTrap = (event: KeyboardEvent) => {\n if (event.key !== 'Tab') return\n\n const focusable = Array.from(\n this.dialog.querySelectorAll<HTMLElement>(\n 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex=\"-1\"])'\n )\n ).filter(el => !el.closest('[inert]'))\n\n if (focusable.length === 0) return\n\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n\n if (event.shiftKey) {\n if (document.activeElement === first) {\n event.preventDefault()\n last.focus()\n }\n } else {\n if (document.activeElement === last) {\n event.preventDefault()\n first.focus()\n }\n }\n }\n\n // Arrow function so `this` is stable for add/removeEventListener pairing.\n private onClose = async () => {\n this.dialog.removeEventListener('keydown', this.handleFocusTrap)\n this._previousFocus?.focus()\n this._previousFocus = null\n this.htmlEl.classList.toggle('overflow-hidden', false)\n await this.delay(500)\n this.resetForm()\n this.resetFrame()\n }\n\n private resetFrame () {\n if (!this.resetFrameValue || (this.frameEl == null)) return\n\n this.frameEl.innerHTML = ''\n this.frameEl.removeAttribute('src')\n }\n\n private resetForm () {\n if (!this.hasClearableFormTarget) return\n this.clearableFormTarget.reset()\n }\n\n async delay (time = 500) {\n return await new Promise((resolve) => {\n setTimeout(resolve, time)\n })\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dialog_component_controller.js","sources":["../../../../../../../app/components/midwest/dialog_component/dialog_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\ndeclare global {\n interface Window {\n Turbo?: { visit(url: string): void }\n }\n}\n\nexport default class Dialog extends Controller<HTMLFormElement> {\n static targets = ['remoteContent', 'clearableForm']\n\n declare readonly hasRemoteContentTarget: boolean\n declare readonly remoteContentTarget: HTMLInputElement\n declare readonly remoteContentTargets: HTMLInputElement[]\n\n declare readonly hasClearableFormTarget: boolean\n declare readonly clearableFormTarget: HTMLFormElement\n declare readonly clearableFormTargets: HTMLFormElement[]\n\n static values = {\n resetFrame: { type: Boolean, default: false },\n history: { type: Boolean, default: false }\n }\n\n declare resetFrameValue: boolean\n declare readonly hasResetFrameValue: boolean\n declare historyValue: boolean\n\n private _previousFocus: HTMLElement | null = null\n // True while a close is being driven by a history navigation (Back button),\n // so onClose knows not to push another history entry in response.\n private _closingViaHistory = false\n\n connect () {\n document.addEventListener('turbo:frame-load', this.bindToggleEls)\n document.addEventListener('turbo:before-visit', this.dismiss)\n document.addEventListener('click', this.handleClickOutside)\n window.addEventListener('popstate', this.handlePopState)\n this.dialog.addEventListener('close', this.onClose)\n this.bindToggleEls()\n }\n\n disconnect () {\n document.removeEventListener('turbo:frame-load', this.bindToggleEls)\n document.removeEventListener('turbo:before-visit', this.dismiss)\n document.removeEventListener('click', this.handleClickOutside)\n window.removeEventListener('popstate', this.handlePopState)\n this.dialog.removeEventListener('close', this.onClose)\n }\n\n // History-aware modals: a Back navigation (popstate) closes the open dialog.\n // The flag tells onClose this close came from history, so it must not push\n // another entry back onto the stack.\n private handlePopState = () => {\n if (!this.historyValue || !this.dialog.open) return\n this._closingViaHistory = true\n this.dialog.close()\n }\n\n get dialog () {\n return this.element\n }\n\n get htmlEl () {\n return document.documentElement\n }\n\n get toggleEls () {\n return document.querySelectorAll(`[data-toggle-dialog=\"${this.dialog.id}\"]`)\n }\n\n get frameEl () {\n return this.dialog.querySelector('turbo-frame')\n }\n\n // Arrow function so `this` is stable for add/removeEventListener pairing.\n private handleClickOutside = (event: Event) => {\n if (event.target !== this.dialog) return\n this.dismiss()\n }\n\n // Arrow function so `this` is stable for add/removeEventListener pairing.\n private bindToggleEls = () => {\n this.toggleEls.forEach((el) => {\n el.addEventListener('click', this.toggle.bind(this))\n })\n }\n\n remoteContentTargetConnected () {\n this.show()\n }\n\n toggle (e: any = undefined) {\n // if clicked elem is a link, prevent default behavior.\n if (e instanceof Event) e.preventDefault()\n this.htmlEl.classList.toggle('overflow-hidden', true)\n\n if (this.dialog.open) {\n this.dialog.close()\n } else {\n this._previousFocus = document.activeElement as HTMLElement\n this.dialog.showModal()\n this.dialog.addEventListener('keydown', this.handleFocusTrap)\n }\n }\n\n show (e: any = undefined) {\n // if clicked elem is a link, prevent default behavior.\n if (e instanceof Event) e.preventDefault()\n this.htmlEl.classList.toggle('overflow-hidden', true)\n this._previousFocus = document.activeElement as HTMLElement\n this.dialog.showModal()\n this.dialog.addEventListener('keydown', this.handleFocusTrap)\n }\n\n hide ({ detail }: { detail: { success: boolean; fetchResponse?: { location: URL } } }) {\n if (!detail.success) return\n const redirectUrl = detail.fetchResponse?.location?.href\n // A redirect supersedes history restoration — suppress the automatic\n // history.back() in onClose so we don't navigate twice.\n if (redirectUrl) this._closingViaHistory = true\n this.dismiss()\n if (redirectUrl) window.Turbo?.visit(redirectUrl)\n }\n\n // Arrow function so `this` is stable for add/removeEventListener pairing.\n dismiss = () => {\n this.dialog.close()\n }\n\n reloadPage () {\n window.location.reload()\n }\n\n private handleFocusTrap = (event: KeyboardEvent) => {\n if (event.key !== 'Tab') return\n\n const focusable = Array.from(\n this.dialog.querySelectorAll<HTMLElement>(\n 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex=\"-1\"])'\n )\n ).filter(el => !el.closest('[inert]'))\n\n if (focusable.length === 0) return\n\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n\n if (event.shiftKey) {\n if (document.activeElement === first) {\n event.preventDefault()\n last.focus()\n }\n } else {\n if (document.activeElement === last) {\n event.preventDefault()\n first.focus()\n }\n }\n }\n\n // Arrow function so `this` is stable for add/removeEventListener pairing.\n private onClose = async () => {\n this.dialog.removeEventListener('keydown', this.handleFocusTrap)\n this._previousFocus?.focus()\n this._previousFocus = null\n this.htmlEl.classList.toggle('overflow-hidden', false)\n this.restoreHistory()\n await this.delay(500)\n this.resetForm()\n this.resetFrame()\n }\n\n // When a history-aware modal is closed manually (Escape, backdrop, close\n // button), pop the modal's advanced URL off the stack so the address bar\n // returns to the page behind it. A close driven by the Back button already\n // moved history, so it is skipped.\n private restoreHistory () {\n if (!this.historyValue) return\n if (this._closingViaHistory) {\n this._closingViaHistory = false\n return\n }\n if (window.history.length > 1) window.history.back()\n }\n\n private resetFrame () {\n if (!this.resetFrameValue || (this.frameEl == null)) return\n\n this.frameEl.innerHTML = ''\n this.frameEl.removeAttribute('src')\n }\n\n private resetForm () {\n if (!this.hasClearableFormTarget) return\n this.clearableFormTarget.reset()\n }\n\n async delay (time = 500) {\n return await new Promise((resolve) => {\n setTimeout(resolve, time)\n })\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
class Form extends Controller {
|
|
4
|
+
static values = {
|
|
5
|
+
// Auto-submit the form (debounced) whenever a field changes.
|
|
6
|
+
autosave: { type: Boolean, default: false },
|
|
7
|
+
autosaveDelay: { type: Number, default: 600 },
|
|
8
|
+
// Emit success/error events and keep buttons interactive after an in-page
|
|
9
|
+
// (non-navigating) Turbo submission.
|
|
10
|
+
ajax: { type: Boolean, default: false }
|
|
11
|
+
};
|
|
12
|
+
autosaveTimer;
|
|
4
13
|
connect() {
|
|
5
14
|
this.element.addEventListener("submit", this.handleSubmit);
|
|
6
15
|
this.element.addEventListener("turbo:submit-end", this.handleTurboEnd);
|
|
16
|
+
if (this.autosaveValue) {
|
|
17
|
+
this.element.addEventListener("input", this.scheduleAutosave);
|
|
18
|
+
this.element.addEventListener("change", this.scheduleAutosave);
|
|
19
|
+
}
|
|
7
20
|
}
|
|
8
21
|
disconnect() {
|
|
9
22
|
this.element.removeEventListener("submit", this.handleSubmit);
|
|
10
23
|
this.element.removeEventListener("turbo:submit-end", this.handleTurboEnd);
|
|
24
|
+
this.element.removeEventListener("input", this.scheduleAutosave);
|
|
25
|
+
this.element.removeEventListener("change", this.scheduleAutosave);
|
|
26
|
+
if (this.autosaveTimer != null)
|
|
27
|
+
clearTimeout(this.autosaveTimer);
|
|
11
28
|
}
|
|
12
29
|
handleSubmit = () => {
|
|
13
30
|
this.submitButtons.forEach((btn) => {
|
|
@@ -15,15 +32,32 @@ class Form extends Controller {
|
|
|
15
32
|
btn.disabled = true;
|
|
16
33
|
});
|
|
17
34
|
};
|
|
18
|
-
// Restore buttons if the submission fails (e.g. a Turbo Frame validation
|
|
19
|
-
// error that re-renders the form without a full page transition).
|
|
20
35
|
handleTurboEnd = (event) => {
|
|
21
|
-
|
|
36
|
+
const detail = event.detail;
|
|
37
|
+
const success = detail?.success ?? false;
|
|
38
|
+
if (!success || this.ajaxValue || this.autosaveValue) {
|
|
22
39
|
this.submitButtons.forEach((btn) => {
|
|
23
40
|
btn.classList.remove("loading");
|
|
24
41
|
btn.disabled = false;
|
|
25
42
|
});
|
|
26
43
|
}
|
|
44
|
+
if (this.ajaxValue) {
|
|
45
|
+
this.dispatch(success ? "success" : "error", { detail });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
// Debounce field changes into a single background submission. `requestSubmit`
|
|
49
|
+
// runs constraint validation and fires the `submit` event, so buttons pick up
|
|
50
|
+
// their loading state and Turbo handles the request just like a manual submit.
|
|
51
|
+
scheduleAutosave = () => {
|
|
52
|
+
if (this.autosaveTimer != null)
|
|
53
|
+
clearTimeout(this.autosaveTimer);
|
|
54
|
+
this.autosaveTimer = setTimeout(() => {
|
|
55
|
+
if (typeof this.element.requestSubmit === "function") {
|
|
56
|
+
this.element.requestSubmit();
|
|
57
|
+
} else {
|
|
58
|
+
this.element.submit();
|
|
59
|
+
}
|
|
60
|
+
}, this.autosaveDelayValue);
|
|
27
61
|
};
|
|
28
62
|
get submitButtons() {
|
|
29
63
|
return Array.from(
|