@unabridged/midwest 0.24.0 → 0.24.4

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 (41) hide show
  1. package/app/assets/javascript/midwest/index.ts +10 -0
  2. package/app/assets/javascript/midwest/map_providers.ts +61 -0
  3. package/app/assets/javascript/midwest/viewport_observer.ts +49 -0
  4. package/app/assets/javascript/midwest.js +1056 -70
  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 +7 -1
  8. package/dist/css/midwest.css +1 -1
  9. package/dist/javascript/collection/app/assets/javascript/midwest/index.js +10 -0
  10. package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
  11. package/dist/javascript/collection/app/assets/javascript/midwest/map_providers.js +56 -0
  12. package/dist/javascript/collection/app/assets/javascript/midwest/map_providers.js.map +1 -0
  13. package/dist/javascript/collection/app/assets/javascript/midwest/viewport_observer.js +22 -0
  14. package/dist/javascript/collection/app/assets/javascript/midwest/viewport_observer.js.map +1 -0
  15. package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js +4 -7
  16. package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js.map +1 -1
  17. package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js +0 -6
  18. package/dist/javascript/collection/app/components/midwest/dropdown_component/dropdown_component_controller.js.map +1 -1
  19. package/dist/javascript/collection/app/components/midwest/form/address_component/address_component_controller.js +441 -0
  20. package/dist/javascript/collection/app/components/midwest/form/address_component/address_component_controller.js.map +1 -0
  21. package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js +55 -1
  22. package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js.map +1 -1
  23. package/dist/javascript/collection/app/components/midwest/intersection_observer_component/intersection_observer_component_controller.js +20 -25
  24. package/dist/javascript/collection/app/components/midwest/intersection_observer_component/intersection_observer_component_controller.js.map +1 -1
  25. package/dist/javascript/collection/app/components/midwest/map_component/map_component_controller.js +297 -0
  26. package/dist/javascript/collection/app/components/midwest/map_component/map_component_controller.js.map +1 -0
  27. package/dist/javascript/collection/app/components/midwest/motion_component/motion_component_controller.js +32 -0
  28. package/dist/javascript/collection/app/components/midwest/motion_component/motion_component_controller.js.map +1 -0
  29. package/dist/javascript/collection/app/components/midwest/notification_component/notification_component_controller.js +4 -12
  30. package/dist/javascript/collection/app/components/midwest/notification_component/notification_component_controller.js.map +1 -1
  31. package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js +34 -5
  32. package/dist/javascript/collection/app/components/midwest/onboarding_component/onboarding_component_controller.js.map +1 -1
  33. package/dist/javascript/collection/app/components/midwest/page_transition_component/page_transition_component_controller.js +38 -0
  34. package/dist/javascript/collection/app/components/midwest/page_transition_component/page_transition_component_controller.js.map +1 -0
  35. package/dist/javascript/collection/app/components/midwest/popover_component/popover_component_controller.js +67 -0
  36. package/dist/javascript/collection/app/components/midwest/popover_component/popover_component_controller.js.map +1 -0
  37. package/dist/javascript/collection/app/components/midwest/table_component/load_more_controller.js +10 -14
  38. package/dist/javascript/collection/app/components/midwest/table_component/load_more_controller.js.map +1 -1
  39. package/dist/javascript/midwest.js +1056 -70
  40. package/dist/javascript/midwest.js.map +1 -1
  41. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"live_summary_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/live_summary_component/live_summary_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Input types that should never appear in a form live summary.\nconst SKIPPED_INPUT_TYPES = new Set([\n 'hidden', 'submit', 'reset', 'button', 'image'\n])\n\ninterface FieldSummary {\n label: string\n value: string\n /** When set, a colour swatch is prepended to the value cell. */\n swatchColor?: string\n}\n\nexport default class FormLiveSummary extends Controller<HTMLElement> {\n static targets = ['list']\n\n declare readonly listTarget: HTMLElement\n\n connect () {\n this.render()\n this.form?.addEventListener('input', this.render)\n this.form?.addEventListener('change', this.render)\n this.form?.addEventListener('midwest-autocomplete:change', this.render)\n this.form?.addEventListener('midwest-color-picker:update', this.render)\n this.form?.addEventListener('midwest-repeatable:change', this.render)\n }\n\n disconnect () {\n this.form?.removeEventListener('input', this.render)\n this.form?.removeEventListener('change', this.render)\n this.form?.removeEventListener('midwest-autocomplete:change', this.render)\n this.form?.removeEventListener('midwest-color-picker:update', this.render)\n this.form?.removeEventListener('midwest-repeatable:change', this.render)\n }\n\n private get form (): HTMLFormElement | null {\n return this.element.closest('form')\n }\n\n private render = () => {\n const fields = this.collectFields()\n\n if (fields.length === 0) {\n this.listTarget.replaceChildren(\n Object.assign(document.createElement('p'), {\n className: 'midwest-form-live-summary-empty',\n textContent: 'No values to display.'\n })\n )\n return\n }\n\n const dl = document.createElement('dl')\n dl.className = 'midwest-form-live-summary-list'\n\n fields.forEach(({ label, value, swatchColor }) => {\n const dt = Object.assign(document.createElement('dt'), {\n className: 'midwest-form-live-summary-term',\n textContent: label\n })\n const dd = document.createElement('dd')\n dd.className = 'midwest-form-live-summary-definition'\n\n if (swatchColor !== undefined) {\n dd.append(this.buildSwatch(swatchColor), value || '—')\n } else {\n dd.textContent = value || '—'\n }\n\n dl.append(dt, dd)\n })\n\n this.listTarget.replaceChildren(dl)\n }\n\n private collectFields (): FieldSummary[] {\n const form = this.form\n if (!form) return []\n\n const fields: FieldSummary[] = []\n const seenRadioNames = new Set<string>()\n\n Array.from(form.elements).forEach(el => {\n const element = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement\n\n if (!element.name) return\n\n // Skip elements that live inside the summary itself.\n if (this.element.contains(element)) return\n\n // Skip elements inside a hidden ShowIf fieldset — they are disabled\n // and excluded from form submission, so omit them from the summary.\n if (element.closest('fieldset.midwest-show-if[hidden]')) return\n\n // Skip all button elements regardless of how they are declared.\n if (element instanceof HTMLButtonElement) return\n\n // Skip tree-view checkboxes — they are collected separately below.\n if (element.closest('.midwest-tree-view')) return\n\n // Skip repeatable fields — templates, destroyed items, and active items\n // are all collected separately below as grouped summaries.\n if (element.closest('.midwest-repeatable')) return\n\n if (element instanceof HTMLInputElement) {\n if (SKIPPED_INPUT_TYPES.has(element.type)) return\n\n if (element.type === 'radio') {\n if (seenRadioNames.has(element.name)) return\n seenRadioNames.add(element.name)\n\n const checked = form.querySelector<HTMLInputElement>(\n `input[type=\"radio\"][name=\"${CSS.escape(element.name)}\"]:checked`\n )\n if (!checked) return\n\n fields.push({\n label: this.groupLabelFor(element),\n value: this.labelFor(checked) || checked.value\n })\n return\n }\n\n if (element.type === 'checkbox') {\n fields.push({\n label: this.labelFor(element),\n value: element.checked ? 'Yes' : 'No'\n })\n return\n }\n }\n\n if (element instanceof HTMLSelectElement) {\n fields.push({\n label: this.labelFor(element),\n value: Array.from(element.selectedOptions).map(o => o.text).join(', ') || '—'\n })\n return\n }\n\n // Text-like inputs and textareas only reach this point.\n fields.push({\n label: this.labelFor(element),\n value: element.value\n })\n })\n\n // Color picker components store their value in a hidden input tagged with\n // data-midwest-color-picker-target=\"input\". The standard loop skips hidden\n // inputs, so query them separately and render with an inline colour swatch.\n form.querySelectorAll<HTMLInputElement>('[data-midwest-color-picker-target=\"input\"]').forEach(input => {\n if (this.element.contains(input)) return\n if (!input.name) return\n\n // Prefer the label inside the picker's own form-group header; fall back\n // to a label[for] match or a prettified version of the input name.\n const picker = input.closest<HTMLElement>('.midwest-color-picker')\n const labelEl = picker?.querySelector<HTMLElement>('.midwest-form-group__top label') ??\n this.form?.querySelector<HTMLElement>(`label[for=\"${CSS.escape(input.id)}\"]`)\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName(input.name.replace(/\\[.*\\]$/, ''))\n\n const value = input.value\n const display = value.startsWith('#') ? value : (value ? value.charAt(0).toUpperCase() + value.slice(1) : '—')\n\n fields.push({ label, value: display, swatchColor: value || undefined })\n })\n\n // Autocomplete components use a nameless combobox input + hidden inputs —\n // neither surfaces through the standard form.elements loop. Query them\n // separately so their selected badge labels appear in the summary.\n // Note: labelFor(input) doesn't work here because InputComponent assigns\n // its own id to the <input>, which won't match the <label>'s for attribute.\n form.querySelectorAll<HTMLElement>('.midwest-autocomplete').forEach(ac => {\n if (this.element.contains(ac)) return\n if (!ac.querySelector('[role=\"combobox\"]')) return\n\n const labelEl = ac.querySelector<HTMLElement>('label')\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName((ac.dataset.name ?? '').replace(/\\[.*\\]$/, ''))\n\n const selected = Array.from(ac.querySelectorAll<HTMLElement>('.midwest-autocomplete-tokens .value'))\n .map(el => el.textContent?.trim())\n .filter(Boolean)\n .join(', ')\n fields.push({ label, value: selected || '—' })\n })\n\n // Date picker components store their value in a hidden input tagged with\n // data-midwest-date-picker-target=\"input\". The standard loop skips hidden\n // inputs, so query the display badge for the human-readable value.\n form.querySelectorAll<HTMLElement>('.midwest-date-picker').forEach(picker => {\n if (this.element.contains(picker)) return\n\n const input = picker.querySelector<HTMLInputElement>('[data-midwest-date-picker-target=\"input\"]')\n if (!input?.name) return\n\n const labelEl = picker.querySelector<HTMLElement>('.midwest-form-group__top label')\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName(input.name.replace(/\\[.*\\]$/, ''))\n\n const display = picker.querySelector<HTMLElement>('[data-midwest-date-picker-target=\"display\"] .value')\n const value = display?.textContent?.trim() || ''\n\n fields.push({ label, value: value || '—' })\n })\n\n // Tree view components render multiple checkboxes sharing the same name\n // (e.g. \"category_ids[]\"). Aggregate the checked nodes into a single\n // comma-separated summary row instead of listing each checkbox separately.\n const seenTreeNames = new Set<string>()\n form.querySelectorAll<HTMLElement>('.midwest-tree-view').forEach(tree => {\n if (this.element.contains(tree)) return\n\n const checked = Array.from(\n tree.querySelectorAll<HTMLInputElement>('.tree-node-checkbox:checked')\n )\n\n // Derive label from the first checkbox's name attribute.\n const firstName = checked[0]?.name || tree.querySelectorAll<HTMLInputElement>('.tree-node-checkbox')[0]?.name\n if (!firstName) return\n if (seenTreeNames.has(firstName)) return\n seenTreeNames.add(firstName)\n\n const label = this.prettifyName(firstName.replace(/\\[\\]$/, ''))\n const values = checked.map(cb => {\n const nodeLabel = cb.closest('.midwest-tree-node')\n ?.querySelector<HTMLElement>(':scope > .tree-node-trigger > .tree-node-label')\n return nodeLabel?.textContent?.trim() || cb.value\n })\n\n fields.push({ label, value: values.join(', ') || '—' })\n })\n\n // Repeatable components contain nested attribute fields grouped inside\n // item wrappers. Summarise each active item as its own row, skipping\n // the hidden clone template and any items marked for destruction.\n form.querySelectorAll<HTMLElement>('.midwest-repeatable').forEach(repeatable => {\n if (this.element.contains(repeatable)) return\n\n const groupLabel = repeatable.querySelector<HTMLElement>('.midwest-form-group__top .midwest-label')\n ?.textContent?.trim() || 'Items'\n\n const items = Array.from(\n repeatable.querySelectorAll<HTMLElement>('[data-midwest-repeatable-target=\"item\"]')\n ).filter(item => {\n const destroy = item.querySelector<HTMLInputElement>('[data-midwest-repeatable-target=\"destroy\"]')\n return destroy?.value !== '1'\n })\n\n items.forEach((item, i) => {\n const values = Array.from(\n item.querySelectorAll<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input:not([type=\"hidden\"]), textarea, select'\n )\n )\n .map(input => {\n if (input instanceof HTMLSelectElement) {\n return Array.from(input.selectedOptions).map(o => o.text).join(', ')\n }\n return input.value\n })\n .filter(Boolean)\n\n fields.push({\n label: items.length > 1 ? `${groupLabel} ${i + 1}` : groupLabel,\n value: values.join(' · ') || '—'\n })\n })\n })\n\n return fields\n }\n\n // Builds a small inline colour swatch to display alongside a colour value.\n // Hex values are applied as an inline background-color; named theme colours\n // use a scoped theme-{color} class so the CSS variable cascade does the work.\n // An empty/transparent value renders a diagonal-stripe placeholder.\n private buildSwatch (color: string): HTMLElement {\n const swatch = document.createElement('span')\n swatch.className = 'midwest-form-live-summary-swatch'\n\n if (color.startsWith('#')) {\n swatch.style.backgroundColor = color\n } else if (color && color !== 'transparent') {\n swatch.classList.add(`theme-${color}`, 'bg-theme-6')\n } else {\n swatch.classList.add('is-transparent')\n }\n\n return swatch\n }\n\n // Finds the label associated with a specific element via `for` attribute,\n // wrapping label, or falls back to a prettified version of the element name.\n private labelFor (el: Element): string {\n const id = (el as HTMLElement).id\n if (id) {\n const label = this.form?.querySelector(`label[for=\"${CSS.escape(id)}\"]`)\n if (label) return label.textContent?.trim() ?? ''\n }\n const wrapper = el.closest('label')\n if (wrapper) return wrapper.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // For radio groups, prefer the containing fieldset legend over the\n // individual radio's label, then fall back to prettifying the name.\n private groupLabelFor (el: Element): string {\n const legend = el.closest('fieldset')?.querySelector('legend')\n if (legend) return legend.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // Converts \"user[first_name]\" or \"first_name\" into \"First name\".\n private prettifyName (name: string): string {\n return name\n .replace(/\\[.*?\\]/g, '')\n .replace(/_/g, ' ')\n .replace(/^\\w/, c => c.toUpperCase())\n .trim()\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"live_summary_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/live_summary_component/live_summary_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Input types that should never appear in a form live summary.\nconst SKIPPED_INPUT_TYPES = new Set([\n 'hidden', 'submit', 'reset', 'button', 'image'\n])\n\ninterface FieldSummary {\n label: string\n value: string\n /** When set, a colour swatch is prepended to the value cell. */\n swatchColor?: string\n /** When set, hovering the value reveals a geocode detail popup. */\n details?: Record<string, string>\n}\n\nexport default class FormLiveSummary extends Controller<HTMLElement> {\n static targets = ['list']\n\n declare readonly listTarget: HTMLElement\n\n connect () {\n this.render()\n this.form?.addEventListener('input', this.render)\n this.form?.addEventListener('change', this.render)\n this.form?.addEventListener('midwest-autocomplete:change', this.render)\n this.form?.addEventListener('midwest-color-picker:update', this.render)\n this.form?.addEventListener('midwest-repeatable:change', this.render)\n this.form?.addEventListener('midwest-address:geocode', this.render)\n }\n\n disconnect () {\n this.form?.removeEventListener('input', this.render)\n this.form?.removeEventListener('change', this.render)\n this.form?.removeEventListener('midwest-autocomplete:change', this.render)\n this.form?.removeEventListener('midwest-color-picker:update', this.render)\n this.form?.removeEventListener('midwest-repeatable:change', this.render)\n this.form?.removeEventListener('midwest-address:geocode', this.render)\n }\n\n private get form (): HTMLFormElement | null {\n return this.element.closest('form')\n }\n\n private render = () => {\n const fields = this.collectFields()\n\n if (fields.length === 0) {\n this.listTarget.replaceChildren(\n Object.assign(document.createElement('p'), {\n className: 'midwest-form-live-summary-empty',\n textContent: 'No values to display.'\n })\n )\n return\n }\n\n const dl = document.createElement('dl')\n dl.className = 'midwest-form-live-summary-list'\n\n fields.forEach(({ label, value, swatchColor, details }) => {\n const dt = Object.assign(document.createElement('dt'), {\n className: 'midwest-form-live-summary-term',\n textContent: label\n })\n const dd = document.createElement('dd')\n dd.className = 'midwest-form-live-summary-definition'\n\n if (swatchColor !== undefined) {\n dd.append(this.buildSwatch(swatchColor), value || '—')\n } else if (details && Object.keys(details).length > 0) {\n dd.append(this.buildGeocode(value || '—', details))\n } else {\n dd.textContent = value || '—'\n }\n\n dl.append(dt, dd)\n })\n\n this.listTarget.replaceChildren(dl)\n }\n\n private collectFields (): FieldSummary[] {\n const form = this.form\n if (!form) return []\n\n const fields: FieldSummary[] = []\n const seenRadioNames = new Set<string>()\n\n Array.from(form.elements).forEach(el => {\n const element = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement\n\n if (!element.name) return\n\n // Skip elements that live inside the summary itself.\n if (this.element.contains(element)) return\n\n // Skip elements inside a hidden ShowIf fieldset — they are disabled\n // and excluded from form submission, so omit them from the summary.\n if (element.closest('fieldset.midwest-show-if[hidden]')) return\n\n // Skip all button elements regardless of how they are declared.\n if (element instanceof HTMLButtonElement) return\n\n // Skip tree-view checkboxes — they are collected separately below.\n if (element.closest('.midwest-tree-view')) return\n\n // Skip repeatable fields — templates, destroyed items, and active items\n // are all collected separately below as grouped summaries.\n if (element.closest('.midwest-repeatable')) return\n\n // Skip address fields — the combobox input and geocode hidden inputs are\n // all collected separately below as a single row with a geocode detail popup.\n if (element.closest('.midwest-address')) return\n\n if (element instanceof HTMLInputElement) {\n if (SKIPPED_INPUT_TYPES.has(element.type)) return\n\n if (element.type === 'radio') {\n if (seenRadioNames.has(element.name)) return\n seenRadioNames.add(element.name)\n\n const checked = form.querySelector<HTMLInputElement>(\n `input[type=\"radio\"][name=\"${CSS.escape(element.name)}\"]:checked`\n )\n if (!checked) return\n\n fields.push({\n label: this.groupLabelFor(element),\n value: this.labelFor(checked) || checked.value\n })\n return\n }\n\n if (element.type === 'checkbox') {\n fields.push({\n label: this.labelFor(element),\n value: element.checked ? 'Yes' : 'No'\n })\n return\n }\n }\n\n if (element instanceof HTMLSelectElement) {\n fields.push({\n label: this.labelFor(element),\n value: Array.from(element.selectedOptions).map(o => o.text).join(', ') || '—'\n })\n return\n }\n\n // Text-like inputs and textareas only reach this point.\n fields.push({\n label: this.labelFor(element),\n value: element.value\n })\n })\n\n // Color picker components store their value in a hidden input tagged with\n // data-midwest-color-picker-target=\"input\". The standard loop skips hidden\n // inputs, so query them separately and render with an inline colour swatch.\n form.querySelectorAll<HTMLInputElement>('[data-midwest-color-picker-target=\"input\"]').forEach(input => {\n if (this.element.contains(input)) return\n if (!input.name) return\n\n // Prefer the label inside the picker's own form-group header; fall back\n // to a label[for] match or a prettified version of the input name.\n const picker = input.closest<HTMLElement>('.midwest-color-picker')\n const labelEl = picker?.querySelector<HTMLElement>('.midwest-form-group__top label') ??\n this.form?.querySelector<HTMLElement>(`label[for=\"${CSS.escape(input.id)}\"]`)\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName(input.name.replace(/\\[.*\\]$/, ''))\n\n const value = input.value\n const display = value.startsWith('#') ? value : (value ? value.charAt(0).toUpperCase() + value.slice(1) : '—')\n\n fields.push({ label, value: display, swatchColor: value || undefined })\n })\n\n // Autocomplete components use a nameless combobox input + hidden inputs —\n // neither surfaces through the standard form.elements loop. Query them\n // separately so their selected badge labels appear in the summary.\n // Note: labelFor(input) doesn't work here because InputComponent assigns\n // its own id to the <input>, which won't match the <label>'s for attribute.\n form.querySelectorAll<HTMLElement>('.midwest-autocomplete').forEach(ac => {\n if (this.element.contains(ac)) return\n if (!ac.querySelector('[role=\"combobox\"]')) return\n\n const labelEl = ac.querySelector<HTMLElement>('label')\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName((ac.dataset.name ?? '').replace(/\\[.*\\]$/, ''))\n\n const selected = Array.from(ac.querySelectorAll<HTMLElement>('.midwest-autocomplete-tokens .value'))\n .map(el => el.textContent?.trim())\n .filter(Boolean)\n .join(', ')\n fields.push({ label, value: selected || '—' })\n })\n\n // Date picker components store their value in a hidden input tagged with\n // data-midwest-date-picker-target=\"input\". The standard loop skips hidden\n // inputs, so query the display badge for the human-readable value.\n form.querySelectorAll<HTMLElement>('.midwest-date-picker').forEach(picker => {\n if (this.element.contains(picker)) return\n\n const input = picker.querySelector<HTMLInputElement>('[data-midwest-date-picker-target=\"input\"]')\n if (!input?.name) return\n\n const labelEl = picker.querySelector<HTMLElement>('.midwest-form-group__top label')\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName(input.name.replace(/\\[.*\\]$/, ''))\n\n const display = picker.querySelector<HTMLElement>('[data-midwest-date-picker-target=\"display\"] .value')\n const value = display?.textContent?.trim() || ''\n\n fields.push({ label, value: value || '—' })\n })\n\n // Tree view components render multiple checkboxes sharing the same name\n // (e.g. \"category_ids[]\"). Aggregate the checked nodes into a single\n // comma-separated summary row instead of listing each checkbox separately.\n const seenTreeNames = new Set<string>()\n form.querySelectorAll<HTMLElement>('.midwest-tree-view').forEach(tree => {\n if (this.element.contains(tree)) return\n\n const checked = Array.from(\n tree.querySelectorAll<HTMLInputElement>('.tree-node-checkbox:checked')\n )\n\n // Derive label from the first checkbox's name attribute.\n const firstName = checked[0]?.name || tree.querySelectorAll<HTMLInputElement>('.tree-node-checkbox')[0]?.name\n if (!firstName) return\n if (seenTreeNames.has(firstName)) return\n seenTreeNames.add(firstName)\n\n const label = this.prettifyName(firstName.replace(/\\[\\]$/, ''))\n const values = checked.map(cb => {\n const nodeLabel = cb.closest('.midwest-tree-node')\n ?.querySelector<HTMLElement>(':scope > .tree-node-trigger > .tree-node-label')\n return nodeLabel?.textContent?.trim() || cb.value\n })\n\n fields.push({ label, value: values.join(', ') || '—' })\n })\n\n // Repeatable components contain nested attribute fields grouped inside\n // item wrappers. Summarise each active item as its own row, skipping\n // the hidden clone template and any items marked for destruction.\n form.querySelectorAll<HTMLElement>('.midwest-repeatable').forEach(repeatable => {\n if (this.element.contains(repeatable)) return\n\n const groupLabel = repeatable.querySelector<HTMLElement>('.midwest-form-group__top .midwest-label')\n ?.textContent?.trim() || 'Items'\n\n const items = Array.from(\n repeatable.querySelectorAll<HTMLElement>('[data-midwest-repeatable-target=\"item\"]')\n ).filter(item => {\n const destroy = item.querySelector<HTMLInputElement>('[data-midwest-repeatable-target=\"destroy\"]')\n return destroy?.value !== '1'\n })\n\n items.forEach((item, i) => {\n const values = Array.from(\n item.querySelectorAll<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input:not([type=\"hidden\"]), textarea, select'\n )\n )\n .map(input => {\n if (input instanceof HTMLSelectElement) {\n return Array.from(input.selectedOptions).map(o => o.text).join(', ')\n }\n return input.value\n })\n .filter(Boolean)\n\n fields.push({\n label: items.length > 1 ? `${groupLabel} ${i + 1}` : groupLabel,\n value: values.join(' · ') || '—'\n })\n })\n })\n\n // Address components display the typed/selected address as plain text. When\n // a place has been geocoded, hovering the value reveals lat, lng, and place\n // ID in a compact popup.\n form.querySelectorAll<HTMLElement>('.midwest-address').forEach(addr => {\n if (this.element.contains(addr)) return\n\n const input = addr.querySelector<HTMLInputElement>('[data-midwest-address-target=\"input\"]')\n const latInput = addr.querySelector<HTMLInputElement>('[data-midwest-address-target=\"latInput\"]')\n const lngInput = addr.querySelector<HTMLInputElement>('[data-midwest-address-target=\"lngInput\"]')\n const placeIdInput = addr.querySelector<HTMLInputElement>('[data-midwest-address-target=\"placeIdInput\"]')\n\n const labelEl = addr.querySelector<HTMLElement>('.midwest-form-group__top label')\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName((input?.name ?? latInput?.name?.replace(/_latitude$/, '') ?? '').replace(/\\[.*\\]$/, ''))\n\n const value = input?.value?.trim() || ''\n\n const details: Record<string, string> = {}\n if (latInput?.value) details['Latitude'] = latInput.value\n if (lngInput?.value) details['Longitude'] = lngInput.value\n if (placeIdInput?.value) details['Place ID'] = placeIdInput.value\n\n fields.push({\n label,\n value: value || '—',\n details: Object.keys(details).length > 0 ? details : undefined\n })\n })\n\n return fields\n }\n\n // Builds a small inline colour swatch to display alongside a colour value.\n // Hex values are applied as an inline background-color; named theme colours\n // use a scoped theme-{color} class so the CSS variable cascade does the work.\n // An empty/transparent value renders a diagonal-stripe placeholder.\n private buildSwatch (color: string): HTMLElement {\n const swatch = document.createElement('span')\n swatch.className = 'midwest-form-live-summary-swatch'\n\n if (color.startsWith('#')) {\n swatch.style.backgroundColor = color\n } else if (color && color !== 'transparent') {\n swatch.classList.add(`theme-${color}`, 'bg-theme-6')\n } else {\n swatch.classList.add('is-transparent')\n }\n\n return swatch\n }\n\n // Wraps an address value in a span that reveals a geocode detail popup on hover.\n private buildGeocode (value: string, details: Record<string, string>): HTMLElement {\n const wrapper = document.createElement('span')\n wrapper.className = 'midwest-form-live-summary-geocode'\n\n const text = document.createElement('span')\n text.className = 'midwest-form-live-summary-geocode-text'\n text.textContent = value\n\n const popup = document.createElement('span')\n popup.className = 'midwest-form-live-summary-geocode-popup'\n popup.setAttribute('aria-hidden', 'true')\n\n Object.entries(details).forEach(([key, val]) => {\n const row = document.createElement('span')\n row.className = 'midwest-form-live-summary-geocode-row'\n\n const keyEl = document.createElement('span')\n keyEl.className = 'midwest-form-live-summary-geocode-key'\n keyEl.textContent = key\n\n const valEl = document.createElement('span')\n valEl.className = 'midwest-form-live-summary-geocode-val'\n valEl.textContent = val\n\n row.append(keyEl, valEl)\n popup.append(row)\n })\n\n wrapper.append(text, popup)\n return wrapper\n }\n\n // Finds the label associated with a specific element via `for` attribute,\n // wrapping label, or falls back to a prettified version of the element name.\n private labelFor (el: Element): string {\n const id = (el as HTMLElement).id\n if (id) {\n const label = this.form?.querySelector(`label[for=\"${CSS.escape(id)}\"]`)\n if (label) return label.textContent?.trim() ?? ''\n }\n const wrapper = el.closest('label')\n if (wrapper) return wrapper.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // For radio groups, prefer the containing fieldset legend over the\n // individual radio's label, then fall back to prettifying the name.\n private groupLabelFor (el: Element): string {\n const legend = el.closest('fieldset')?.querySelector('legend')\n if (legend) return legend.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // Converts \"user[first_name]\" or \"first_name\" into \"First name\".\n private prettifyName (name: string): string {\n return name\n .replace(/\\[.*?\\]/g, '')\n .replace(/_/g, ' ')\n .replace(/^\\w/, c => c.toUpperCase())\n .trim()\n }\n}\n"],"names":[],"mappings":";;AAGA,MAAM,mBAAA,uBAA0B,GAAI,CAAA;AAAA,EAClC,QAAA;AAAA,EAAU,QAAA;AAAA,EAAU,OAAA;AAAA,EAAS,QAAA;AAAA,EAAU,OAAA;AACzC,CAAC,CAAA,CAAA;AAWD,MAAqB,wBAAwB,UAAwB,CAAA;AAAA,EACnE,OAAO,OAAU,GAAA,CAAC,MAAM,CAAA,CAAA;AAAA,EAIxB,OAAW,GAAA;AACT,IAAA,IAAA,CAAK,MAAO,EAAA,CAAA;AACZ,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,OAAS,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAChD,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,QAAU,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACjD,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,6BAA+B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACtE,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,6BAA+B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACtE,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,2BAA6B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACpE,IAAA,IAAA,CAAK,IAAM,EAAA,gBAAA,CAAiB,yBAA2B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAAA,GACpE;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,OAAS,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACnD,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,QAAU,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACpD,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,6BAA+B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACzE,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,6BAA+B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACzE,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,2BAA6B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACvE,IAAA,IAAA,CAAK,IAAM,EAAA,mBAAA,CAAoB,yBAA2B,EAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAAA,GACvE;AAAA,EAEA,IAAY,IAAgC,GAAA;AAC1C,IAAO,OAAA,IAAA,CAAK,OAAQ,CAAA,OAAA,CAAQ,MAAM,CAAA,CAAA;AAAA,GACpC;AAAA,EAEQ,SAAS,MAAM;AACrB,IAAM,MAAA,MAAA,GAAS,KAAK,aAAc,EAAA,CAAA;AAElC,IAAI,IAAA,MAAA,CAAO,WAAW,CAAG,EAAA;AACvB,MAAA,IAAA,CAAK,UAAW,CAAA,eAAA;AAAA,QACd,MAAO,CAAA,MAAA,CAAO,QAAS,CAAA,aAAA,CAAc,GAAG,CAAG,EAAA;AAAA,UACzC,SAAW,EAAA,iCAAA;AAAA,UACX,WAAa,EAAA,uBAAA;AAAA,SACd,CAAA;AAAA,OACH,CAAA;AACA,MAAA,OAAA;AAAA,KACF;AAEA,IAAM,MAAA,EAAA,GAAK,QAAS,CAAA,aAAA,CAAc,IAAI,CAAA,CAAA;AACtC,IAAA,EAAA,CAAG,SAAY,GAAA,gCAAA,CAAA;AAEf,IAAA,MAAA,CAAO,QAAQ,CAAC,EAAE,OAAO,KAAO,EAAA,WAAA,EAAa,SAAc,KAAA;AACzD,MAAA,MAAM,KAAK,MAAO,CAAA,MAAA,CAAO,QAAS,CAAA,aAAA,CAAc,IAAI,CAAG,EAAA;AAAA,QACrD,SAAW,EAAA,gCAAA;AAAA,QACX,WAAa,EAAA,KAAA;AAAA,OACd,CAAA,CAAA;AACD,MAAM,MAAA,EAAA,GAAK,QAAS,CAAA,aAAA,CAAc,IAAI,CAAA,CAAA;AACtC,MAAA,EAAA,CAAG,SAAY,GAAA,sCAAA,CAAA;AAEf,MAAA,IAAI,gBAAgB,KAAW,CAAA,EAAA;AAC7B,QAAA,EAAA,CAAG,OAAO,IAAK,CAAA,WAAA,CAAY,WAAW,CAAA,EAAG,SAAS,QAAG,CAAA,CAAA;AAAA,iBAC5C,OAAW,IAAA,MAAA,CAAO,KAAK,OAAO,CAAA,CAAE,SAAS,CAAG,EAAA;AACrD,QAAA,EAAA,CAAG,OAAO,IAAK,CAAA,YAAA,CAAa,KAAS,IAAA,QAAA,EAAK,OAAO,CAAC,CAAA,CAAA;AAAA,OAC7C,MAAA;AACL,QAAA,EAAA,CAAG,cAAc,KAAS,IAAA,QAAA,CAAA;AAAA,OAC5B;AAEA,MAAG,EAAA,CAAA,MAAA,CAAO,IAAI,EAAE,CAAA,CAAA;AAAA,KACjB,CAAA,CAAA;AAED,IAAK,IAAA,CAAA,UAAA,CAAW,gBAAgB,EAAE,CAAA,CAAA;AAAA,GACpC,CAAA;AAAA,EAEQ,aAAiC,GAAA;AACvC,IAAA,MAAM,OAAO,IAAK,CAAA,IAAA,CAAA;AAClB,IAAA,IAAI,CAAC,IAAA;AAAM,MAAA,OAAO,EAAC,CAAA;AAEnB,IAAA,MAAM,SAAyB,EAAC,CAAA;AAChC,IAAM,MAAA,cAAA,uBAAqB,GAAY,EAAA,CAAA;AAEvC,IAAA,KAAA,CAAM,IAAK,CAAA,IAAA,CAAK,QAAQ,CAAA,CAAE,QAAQ,CAAM,EAAA,KAAA;AACtC,MAAA,MAAM,OAAU,GAAA,EAAA,CAAA;AAEhB,MAAA,IAAI,CAAC,OAAQ,CAAA,IAAA;AAAM,QAAA,OAAA;AAGnB,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,OAAO,CAAA;AAAG,QAAA,OAAA;AAIpC,MAAI,IAAA,OAAA,CAAQ,QAAQ,kCAAkC,CAAA;AAAG,QAAA,OAAA;AAGzD,MAAA,IAAI,OAAmB,YAAA,iBAAA;AAAmB,QAAA,OAAA;AAG1C,MAAI,IAAA,OAAA,CAAQ,QAAQ,oBAAoB,CAAA;AAAG,QAAA,OAAA;AAI3C,MAAI,IAAA,OAAA,CAAQ,QAAQ,qBAAqB,CAAA;AAAG,QAAA,OAAA;AAI5C,MAAI,IAAA,OAAA,CAAQ,QAAQ,kBAAkB,CAAA;AAAG,QAAA,OAAA;AAEzC,MAAA,IAAI,mBAAmB,gBAAkB,EAAA;AACvC,QAAI,IAAA,mBAAA,CAAoB,GAAI,CAAA,OAAA,CAAQ,IAAI,CAAA;AAAG,UAAA,OAAA;AAE3C,QAAI,IAAA,OAAA,CAAQ,SAAS,OAAS,EAAA;AAC5B,UAAI,IAAA,cAAA,CAAe,GAAI,CAAA,OAAA,CAAQ,IAAI,CAAA;AAAG,YAAA,OAAA;AACtC,UAAe,cAAA,CAAA,GAAA,CAAI,QAAQ,IAAI,CAAA,CAAA;AAE/B,UAAA,MAAM,UAAU,IAAK,CAAA,aAAA;AAAA,YACnB,CAA6B,0BAAA,EAAA,GAAA,CAAI,MAAO,CAAA,OAAA,CAAQ,IAAI,CAAA,CAAA,UAAA,CAAA;AAAA,WACtD,CAAA;AACA,UAAA,IAAI,CAAC,OAAA;AAAS,YAAA,OAAA;AAEd,UAAA,MAAA,CAAO,IAAK,CAAA;AAAA,YACV,KAAA,EAAO,IAAK,CAAA,aAAA,CAAc,OAAO,CAAA;AAAA,YACjC,KAAO,EAAA,IAAA,CAAK,QAAS,CAAA,OAAO,KAAK,OAAQ,CAAA,KAAA;AAAA,WAC1C,CAAA,CAAA;AACD,UAAA,OAAA;AAAA,SACF;AAEA,QAAI,IAAA,OAAA,CAAQ,SAAS,UAAY,EAAA;AAC/B,UAAA,MAAA,CAAO,IAAK,CAAA;AAAA,YACV,KAAA,EAAO,IAAK,CAAA,QAAA,CAAS,OAAO,CAAA;AAAA,YAC5B,KAAA,EAAO,OAAQ,CAAA,OAAA,GAAU,KAAQ,GAAA,IAAA;AAAA,WAClC,CAAA,CAAA;AACD,UAAA,OAAA;AAAA,SACF;AAAA,OACF;AAEA,MAAA,IAAI,mBAAmB,iBAAmB,EAAA;AACxC,QAAA,MAAA,CAAO,IAAK,CAAA;AAAA,UACV,KAAA,EAAO,IAAK,CAAA,QAAA,CAAS,OAAO,CAAA;AAAA,UAC5B,KAAO,EAAA,KAAA,CAAM,IAAK,CAAA,OAAA,CAAQ,eAAe,CAAA,CAAE,GAAI,CAAA,CAAA,CAAA,KAAK,CAAE,CAAA,IAAI,CAAE,CAAA,IAAA,CAAK,IAAI,CAAK,IAAA,QAAA;AAAA,SAC3E,CAAA,CAAA;AACD,QAAA,OAAA;AAAA,OACF;AAGA,MAAA,MAAA,CAAO,IAAK,CAAA;AAAA,QACV,KAAA,EAAO,IAAK,CAAA,QAAA,CAAS,OAAO,CAAA;AAAA,QAC5B,OAAO,OAAQ,CAAA,KAAA;AAAA,OAChB,CAAA,CAAA;AAAA,KACF,CAAA,CAAA;AAKD,IAAA,IAAA,CAAK,gBAAmC,CAAA,4CAA4C,CAAE,CAAA,OAAA,CAAQ,CAAS,KAAA,KAAA;AACrG,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,KAAK,CAAA;AAAG,QAAA,OAAA;AAClC,MAAA,IAAI,CAAC,KAAM,CAAA,IAAA;AAAM,QAAA,OAAA;AAIjB,MAAM,MAAA,MAAA,GAAS,KAAM,CAAA,OAAA,CAAqB,uBAAuB,CAAA,CAAA;AACjE,MAAA,MAAM,OAAU,GAAA,MAAA,EAAQ,aAA2B,CAAA,gCAAgC,CACjF,IAAA,IAAA,CAAK,IAAM,EAAA,aAAA,CAA2B,CAAc,WAAA,EAAA,GAAA,CAAI,MAAO,CAAA,KAAA,CAAM,EAAE,CAAK,CAAA,EAAA,CAAA,CAAA,CAAA;AAC9E,MAAA,MAAM,KAAQ,GAAA,OAAA,EAAS,WAAa,EAAA,IAAA,EAClC,IAAA,IAAA,CAAK,YAAa,CAAA,KAAA,CAAM,IAAK,CAAA,OAAA,CAAQ,SAAW,EAAA,EAAE,CAAC,CAAA,CAAA;AAErD,MAAA,MAAM,QAAQ,KAAM,CAAA,KAAA,CAAA;AACpB,MAAA,MAAM,UAAU,KAAM,CAAA,UAAA,CAAW,GAAG,CAAA,GAAI,QAAS,KAAQ,GAAA,KAAA,CAAM,MAAO,CAAA,CAAC,EAAE,WAAY,EAAA,GAAI,KAAM,CAAA,KAAA,CAAM,CAAC,CAAI,GAAA,QAAA,CAAA;AAE1G,MAAO,MAAA,CAAA,IAAA,CAAK,EAAE,KAAO,EAAA,KAAA,EAAO,SAAS,WAAa,EAAA,KAAA,IAAS,QAAW,CAAA,CAAA;AAAA,KACvE,CAAA,CAAA;AAOD,IAAA,IAAA,CAAK,gBAA8B,CAAA,uBAAuB,CAAE,CAAA,OAAA,CAAQ,CAAM,EAAA,KAAA;AACxE,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,EAAE,CAAA;AAAG,QAAA,OAAA;AAC/B,MAAI,IAAA,CAAC,EAAG,CAAA,aAAA,CAAc,mBAAmB,CAAA;AAAG,QAAA,OAAA;AAE5C,MAAM,MAAA,OAAA,GAAU,EAAG,CAAA,aAAA,CAA2B,OAAO,CAAA,CAAA;AACrD,MAAA,MAAM,KAAQ,GAAA,OAAA,EAAS,WAAa,EAAA,IAAA,MAClC,IAAK,CAAA,YAAA,CAAA,CAAc,EAAG,CAAA,OAAA,CAAQ,IAAQ,IAAA,EAAA,EAAI,OAAQ,CAAA,SAAA,EAAW,EAAE,CAAC,CAAA,CAAA;AAElE,MAAA,MAAM,WAAW,KAAM,CAAA,IAAA,CAAK,GAAG,gBAA8B,CAAA,qCAAqC,CAAC,CAChG,CAAA,GAAA,CAAI,QAAM,EAAG,CAAA,WAAA,EAAa,MAAM,CAAA,CAChC,OAAO,OAAO,CAAA,CACd,KAAK,IAAI,CAAA,CAAA;AACZ,MAAA,MAAA,CAAO,KAAK,EAAE,KAAA,EAAO,KAAO,EAAA,QAAA,IAAY,UAAK,CAAA,CAAA;AAAA,KAC9C,CAAA,CAAA;AAKD,IAAA,IAAA,CAAK,gBAA8B,CAAA,sBAAsB,CAAE,CAAA,OAAA,CAAQ,CAAU,MAAA,KAAA;AAC3E,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,MAAM,CAAA;AAAG,QAAA,OAAA;AAEnC,MAAM,MAAA,KAAA,GAAQ,MAAO,CAAA,aAAA,CAAgC,2CAA2C,CAAA,CAAA;AAChG,MAAA,IAAI,CAAC,KAAO,EAAA,IAAA;AAAM,QAAA,OAAA;AAElB,MAAM,MAAA,OAAA,GAAU,MAAO,CAAA,aAAA,CAA2B,gCAAgC,CAAA,CAAA;AAClF,MAAA,MAAM,KAAQ,GAAA,OAAA,EAAS,WAAa,EAAA,IAAA,EAClC,IAAA,IAAA,CAAK,YAAa,CAAA,KAAA,CAAM,IAAK,CAAA,OAAA,CAAQ,SAAW,EAAA,EAAE,CAAC,CAAA,CAAA;AAErD,MAAM,MAAA,OAAA,GAAU,MAAO,CAAA,aAAA,CAA2B,oDAAoD,CAAA,CAAA;AACtG,MAAA,MAAM,KAAQ,GAAA,OAAA,EAAS,WAAa,EAAA,IAAA,EAAU,IAAA,EAAA,CAAA;AAE9C,MAAA,MAAA,CAAO,KAAK,EAAE,KAAA,EAAO,KAAO,EAAA,KAAA,IAAS,UAAK,CAAA,CAAA;AAAA,KAC3C,CAAA,CAAA;AAKD,IAAM,MAAA,aAAA,uBAAoB,GAAY,EAAA,CAAA;AACtC,IAAA,IAAA,CAAK,gBAA8B,CAAA,oBAAoB,CAAE,CAAA,OAAA,CAAQ,CAAQ,IAAA,KAAA;AACvE,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,IAAI,CAAA;AAAG,QAAA,OAAA;AAEjC,MAAA,MAAM,UAAU,KAAM,CAAA,IAAA;AAAA,QACpB,IAAA,CAAK,iBAAmC,6BAA6B,CAAA;AAAA,OACvE,CAAA;AAGA,MAAM,MAAA,SAAA,GAAY,OAAQ,CAAA,CAAC,CAAG,EAAA,IAAA,IAAQ,KAAK,gBAAmC,CAAA,qBAAqB,CAAE,CAAA,CAAC,CAAG,EAAA,IAAA,CAAA;AACzG,MAAA,IAAI,CAAC,SAAA;AAAW,QAAA,OAAA;AAChB,MAAI,IAAA,aAAA,CAAc,IAAI,SAAS,CAAA;AAAG,QAAA,OAAA;AAClC,MAAA,aAAA,CAAc,IAAI,SAAS,CAAA,CAAA;AAE3B,MAAA,MAAM,QAAQ,IAAK,CAAA,YAAA,CAAa,UAAU,OAAQ,CAAA,OAAA,EAAS,EAAE,CAAC,CAAA,CAAA;AAC9D,MAAM,MAAA,MAAA,GAAS,OAAQ,CAAA,GAAA,CAAI,CAAM,EAAA,KAAA;AAC/B,QAAA,MAAM,YAAY,EAAG,CAAA,OAAA,CAAQ,oBAAoB,CAAA,EAC7C,cAA2B,gDAAgD,CAAA,CAAA;AAC/E,QAAA,OAAO,SAAW,EAAA,WAAA,EAAa,IAAK,EAAA,IAAK,EAAG,CAAA,KAAA,CAAA;AAAA,OAC7C,CAAA,CAAA;AAED,MAAO,MAAA,CAAA,IAAA,CAAK,EAAE,KAAO,EAAA,KAAA,EAAO,OAAO,IAAK,CAAA,IAAI,CAAK,IAAA,QAAA,EAAK,CAAA,CAAA;AAAA,KACvD,CAAA,CAAA;AAKD,IAAA,IAAA,CAAK,gBAA8B,CAAA,qBAAqB,CAAE,CAAA,OAAA,CAAQ,CAAc,UAAA,KAAA;AAC9E,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,UAAU,CAAA;AAAG,QAAA,OAAA;AAEvC,MAAA,MAAM,aAAa,UAAW,CAAA,aAAA,CAA2B,yCAAyC,CAC9F,EAAA,WAAA,EAAa,MAAU,IAAA,OAAA,CAAA;AAE3B,MAAA,MAAM,QAAQ,KAAM,CAAA,IAAA;AAAA,QAClB,UAAA,CAAW,iBAA8B,yCAAyC,CAAA;AAAA,OACpF,CAAE,OAAO,CAAQ,IAAA,KAAA;AACf,QAAM,MAAA,OAAA,GAAU,IAAK,CAAA,aAAA,CAAgC,4CAA4C,CAAA,CAAA;AACjG,QAAA,OAAO,SAAS,KAAU,KAAA,GAAA,CAAA;AAAA,OAC3B,CAAA,CAAA;AAED,MAAM,KAAA,CAAA,OAAA,CAAQ,CAAC,IAAA,EAAM,CAAM,KAAA;AACzB,QAAA,MAAM,SAAS,KAAM,CAAA,IAAA;AAAA,UACnB,IAAK,CAAA,gBAAA;AAAA,YACH,8CAAA;AAAA,WACF;AAAA,SACF,CACG,IAAI,CAAS,KAAA,KAAA;AACZ,UAAA,IAAI,iBAAiB,iBAAmB,EAAA;AACtC,YAAO,OAAA,KAAA,CAAM,IAAK,CAAA,KAAA,CAAM,eAAe,CAAA,CAAE,GAAI,CAAA,CAAA,CAAA,KAAK,CAAE,CAAA,IAAI,CAAE,CAAA,IAAA,CAAK,IAAI,CAAA,CAAA;AAAA,WACrE;AACA,UAAA,OAAO,KAAM,CAAA,KAAA,CAAA;AAAA,SACd,CACA,CAAA,MAAA,CAAO,OAAO,CAAA,CAAA;AAEjB,QAAA,MAAA,CAAO,IAAK,CAAA;AAAA,UACV,OAAO,KAAM,CAAA,MAAA,GAAS,IAAI,CAAG,EAAA,UAAA,CAAA,CAAA,EAAc,IAAI,CAAM,CAAA,CAAA,GAAA,UAAA;AAAA,UACrD,KAAO,EAAA,MAAA,CAAO,IAAK,CAAA,QAAK,CAAK,IAAA,QAAA;AAAA,SAC9B,CAAA,CAAA;AAAA,OACF,CAAA,CAAA;AAAA,KACF,CAAA,CAAA;AAKD,IAAA,IAAA,CAAK,gBAA8B,CAAA,kBAAkB,CAAE,CAAA,OAAA,CAAQ,CAAQ,IAAA,KAAA;AACrE,MAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,QAAA,CAAS,IAAI,CAAA;AAAG,QAAA,OAAA;AAEjC,MAAM,MAAA,KAAA,GAAQ,IAAK,CAAA,aAAA,CAAgC,uCAAuC,CAAA,CAAA;AAC1F,MAAM,MAAA,QAAA,GAAW,IAAK,CAAA,aAAA,CAAgC,0CAA0C,CAAA,CAAA;AAChG,MAAM,MAAA,QAAA,GAAW,IAAK,CAAA,aAAA,CAAgC,0CAA0C,CAAA,CAAA;AAChG,MAAM,MAAA,YAAA,GAAe,IAAK,CAAA,aAAA,CAAgC,8CAA8C,CAAA,CAAA;AAExG,MAAM,MAAA,OAAA,GAAU,IAAK,CAAA,aAAA,CAA2B,gCAAgC,CAAA,CAAA;AAChF,MAAA,MAAM,QAAQ,OAAS,EAAA,WAAA,EAAa,MAClC,IAAA,IAAA,CAAK,cAAc,KAAO,EAAA,IAAA,IAAQ,UAAU,IAAM,EAAA,OAAA,CAAQ,cAAc,EAAE,CAAA,IAAK,IAAI,OAAQ,CAAA,SAAA,EAAW,EAAE,CAAC,CAAA,CAAA;AAE3G,MAAA,MAAM,KAAQ,GAAA,KAAA,EAAO,KAAO,EAAA,IAAA,EAAU,IAAA,EAAA,CAAA;AAEtC,MAAA,MAAM,UAAkC,EAAC,CAAA;AACzC,MAAA,IAAI,QAAU,EAAA,KAAA;AAAW,QAAQ,OAAA,CAAA,UAAU,IAAK,QAAS,CAAA,KAAA,CAAA;AACzD,MAAA,IAAI,QAAU,EAAA,KAAA;AAAW,QAAQ,OAAA,CAAA,WAAW,IAAI,QAAS,CAAA,KAAA,CAAA;AACzD,MAAA,IAAI,YAAc,EAAA,KAAA;AAAO,QAAQ,OAAA,CAAA,UAAU,IAAK,YAAa,CAAA,KAAA,CAAA;AAE7D,MAAA,MAAA,CAAO,IAAK,CAAA;AAAA,QACV,KAAA;AAAA,QACA,OAAO,KAAS,IAAA,QAAA;AAAA,QAChB,SAAS,MAAO,CAAA,IAAA,CAAK,OAAO,CAAE,CAAA,MAAA,GAAS,IAAI,OAAU,GAAA,KAAA,CAAA;AAAA,OACtD,CAAA,CAAA;AAAA,KACF,CAAA,CAAA;AAED,IAAO,OAAA,MAAA,CAAA;AAAA,GACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,YAAa,KAA4B,EAAA;AAC/C,IAAM,MAAA,MAAA,GAAS,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AAC5C,IAAA,MAAA,CAAO,SAAY,GAAA,kCAAA,CAAA;AAEnB,IAAI,IAAA,KAAA,CAAM,UAAW,CAAA,GAAG,CAAG,EAAA;AACzB,MAAA,MAAA,CAAO,MAAM,eAAkB,GAAA,KAAA,CAAA;AAAA,KACjC,MAAA,IAAW,KAAS,IAAA,KAAA,KAAU,aAAe,EAAA;AAC3C,MAAA,MAAA,CAAO,SAAU,CAAA,GAAA,CAAI,CAAS,MAAA,EAAA,KAAA,CAAA,CAAA,EAAS,YAAY,CAAA,CAAA;AAAA,KAC9C,MAAA;AACL,MAAO,MAAA,CAAA,SAAA,CAAU,IAAI,gBAAgB,CAAA,CAAA;AAAA,KACvC;AAEA,IAAO,OAAA,MAAA,CAAA;AAAA,GACT;AAAA;AAAA,EAGQ,YAAA,CAAc,OAAe,OAA8C,EAAA;AACjF,IAAM,MAAA,OAAA,GAAU,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AAC7C,IAAA,OAAA,CAAQ,SAAY,GAAA,mCAAA,CAAA;AAEpB,IAAM,MAAA,IAAA,GAAO,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AAC1C,IAAA,IAAA,CAAK,SAAY,GAAA,wCAAA,CAAA;AACjB,IAAA,IAAA,CAAK,WAAc,GAAA,KAAA,CAAA;AAEnB,IAAM,MAAA,KAAA,GAAQ,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AAC3C,IAAA,KAAA,CAAM,SAAY,GAAA,yCAAA,CAAA;AAClB,IAAM,KAAA,CAAA,YAAA,CAAa,eAAe,MAAM,CAAA,CAAA;AAExC,IAAO,MAAA,CAAA,OAAA,CAAQ,OAAO,CAAE,CAAA,OAAA,CAAQ,CAAC,CAAC,GAAA,EAAK,GAAG,CAAM,KAAA;AAC9C,MAAM,MAAA,GAAA,GAAM,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AACzC,MAAA,GAAA,CAAI,SAAY,GAAA,uCAAA,CAAA;AAEhB,MAAM,MAAA,KAAA,GAAQ,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AAC3C,MAAA,KAAA,CAAM,SAAY,GAAA,uCAAA,CAAA;AAClB,MAAA,KAAA,CAAM,WAAc,GAAA,GAAA,CAAA;AAEpB,MAAM,MAAA,KAAA,GAAQ,QAAS,CAAA,aAAA,CAAc,MAAM,CAAA,CAAA;AAC3C,MAAA,KAAA,CAAM,SAAY,GAAA,uCAAA,CAAA;AAClB,MAAA,KAAA,CAAM,WAAc,GAAA,GAAA,CAAA;AAEpB,MAAI,GAAA,CAAA,MAAA,CAAO,OAAO,KAAK,CAAA,CAAA;AACvB,MAAA,KAAA,CAAM,OAAO,GAAG,CAAA,CAAA;AAAA,KACjB,CAAA,CAAA;AAED,IAAQ,OAAA,CAAA,MAAA,CAAO,MAAM,KAAK,CAAA,CAAA;AAC1B,IAAO,OAAA,OAAA,CAAA;AAAA,GACT;AAAA;AAAA;AAAA,EAIQ,SAAU,EAAqB,EAAA;AACrC,IAAA,MAAM,KAAM,EAAmB,CAAA,EAAA,CAAA;AAC/B,IAAA,IAAI,EAAI,EAAA;AACN,MAAM,MAAA,KAAA,GAAQ,KAAK,IAAM,EAAA,aAAA,CAAc,cAAc,GAAI,CAAA,MAAA,CAAO,EAAE,CAAK,CAAA,EAAA,CAAA,CAAA,CAAA;AACvE,MAAI,IAAA,KAAA;AAAO,QAAO,OAAA,KAAA,CAAM,WAAa,EAAA,IAAA,EAAU,IAAA,EAAA,CAAA;AAAA,KACjD;AACA,IAAM,MAAA,OAAA,GAAU,EAAG,CAAA,OAAA,CAAQ,OAAO,CAAA,CAAA;AAClC,IAAI,IAAA,OAAA;AAAS,MAAO,OAAA,OAAA,CAAQ,WAAa,EAAA,IAAA,EAAU,IAAA,EAAA,CAAA;AACnD,IAAA,OAAO,IAAK,CAAA,YAAA,CAAc,EAAwB,CAAA,IAAA,IAAQ,EAAE,CAAA,CAAA;AAAA,GAC9D;AAAA;AAAA;AAAA,EAIQ,cAAe,EAAqB,EAAA;AAC1C,IAAA,MAAM,SAAS,EAAG,CAAA,OAAA,CAAQ,UAAU,CAAA,EAAG,cAAc,QAAQ,CAAA,CAAA;AAC7D,IAAI,IAAA,MAAA;AAAQ,MAAO,OAAA,MAAA,CAAO,WAAa,EAAA,IAAA,EAAU,IAAA,EAAA,CAAA;AACjD,IAAA,OAAO,IAAK,CAAA,YAAA,CAAc,EAAwB,CAAA,IAAA,IAAQ,EAAE,CAAA,CAAA;AAAA,GAC9D;AAAA;AAAA,EAGQ,aAAc,IAAsB,EAAA;AAC1C,IAAA,OAAO,KACJ,OAAQ,CAAA,UAAA,EAAY,EAAE,CAAA,CACtB,QAAQ,IAAM,EAAA,GAAG,CACjB,CAAA,OAAA,CAAQ,OAAO,CAAK,CAAA,KAAA,CAAA,CAAE,WAAY,EAAC,EACnC,IAAK,EAAA,CAAA;AAAA,GACV;AACF;;;;"}
@@ -1,4 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
+ import { observeViewport } from '../../../assets/javascript/midwest/viewport_observer.js';
2
3
 
3
4
  class ViewportObserver extends Controller {
4
5
  static values = {
@@ -7,35 +8,29 @@ class ViewportObserver extends Controller {
7
8
  once: { type: Boolean, default: false },
8
9
  visibleClass: { type: String, default: "" }
9
10
  };
10
- observer = null;
11
+ teardown = null;
11
12
  connect() {
12
- this.observer = new IntersectionObserver(
13
- (entries) => {
14
- for (const entry of entries)
15
- this.handle(entry);
13
+ this.teardown = observeViewport(this.element, {
14
+ threshold: this.thresholdValue,
15
+ rootMargin: this.rootMarginValue,
16
+ once: this.onceValue,
17
+ onEnter: (entry) => {
18
+ this.element.setAttribute("data-intersecting", "true");
19
+ if (this.visibleClassValue)
20
+ this.element.classList.add(this.visibleClassValue);
21
+ this.dispatch("enter", { detail: { entry }, bubbles: true });
16
22
  },
17
- { threshold: this.thresholdValue, rootMargin: this.rootMarginValue }
18
- );
19
- this.observer.observe(this.element);
23
+ onLeave: (entry) => {
24
+ this.element.setAttribute("data-intersecting", "false");
25
+ if (this.visibleClassValue)
26
+ this.element.classList.remove(this.visibleClassValue);
27
+ this.dispatch("leave", { detail: { entry }, bubbles: true });
28
+ }
29
+ });
20
30
  }
21
31
  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
- }
32
+ this.teardown?.();
33
+ this.teardown = null;
39
34
  }
40
35
  }
41
36
 
@@ -1 +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;;;;"}
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'\nimport { observeViewport } from '../../../assets/javascript/midwest/viewport_observer'\n\n// Declarative viewport detection. Wraps the shared viewport observer 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 teardown: (() => void) | null = null\n\n connect () {\n this.teardown = observeViewport(this.element, {\n threshold: this.thresholdValue,\n rootMargin: this.rootMarginValue,\n once: this.onceValue,\n onEnter: (entry) => {\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 onLeave: (entry) => {\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\n disconnect () {\n this.teardown?.()\n this.teardown = null\n }\n}\n"],"names":[],"mappings":";;;AAcA,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,QAAgC,GAAA,IAAA,CAAA;AAAA,EAExC,OAAW,GAAA;AACT,IAAK,IAAA,CAAA,QAAA,GAAW,eAAgB,CAAA,IAAA,CAAK,OAAS,EAAA;AAAA,MAC5C,WAAW,IAAK,CAAA,cAAA;AAAA,MAChB,YAAY,IAAK,CAAA,eAAA;AAAA,MACjB,MAAM,IAAK,CAAA,SAAA;AAAA,MACX,OAAA,EAAS,CAAC,KAAU,KAAA;AAClB,QAAK,IAAA,CAAA,OAAA,CAAQ,YAAa,CAAA,mBAAA,EAAqB,MAAM,CAAA,CAAA;AACrD,QAAA,IAAI,IAAK,CAAA,iBAAA;AAAmB,UAAA,IAAA,CAAK,OAAQ,CAAA,SAAA,CAAU,GAAI,CAAA,IAAA,CAAK,iBAAiB,CAAA,CAAA;AAC7E,QAAK,IAAA,CAAA,QAAA,CAAS,SAAS,EAAE,MAAA,EAAQ,EAAE,KAAM,EAAA,EAAG,OAAS,EAAA,IAAA,EAAM,CAAA,CAAA;AAAA,OAC7D;AAAA,MACA,OAAA,EAAS,CAAC,KAAU,KAAA;AAClB,QAAK,IAAA,CAAA,OAAA,CAAQ,YAAa,CAAA,mBAAA,EAAqB,OAAO,CAAA,CAAA;AACtD,QAAA,IAAI,IAAK,CAAA,iBAAA;AAAmB,UAAA,IAAA,CAAK,OAAQ,CAAA,SAAA,CAAU,MAAO,CAAA,IAAA,CAAK,iBAAiB,CAAA,CAAA;AAChF,QAAK,IAAA,CAAA,QAAA,CAAS,SAAS,EAAE,MAAA,EAAQ,EAAE,KAAM,EAAA,EAAG,OAAS,EAAA,IAAA,EAAM,CAAA,CAAA;AAAA,OAC7D;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,QAAW,IAAA,CAAA;AAChB,IAAA,IAAA,CAAK,QAAW,GAAA,IAAA,CAAA;AAAA,GAClB;AACF;;;;"}
@@ -0,0 +1,297 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ import { loadMapKitScript, loadGoogleMapsScript, loadLeafletScript } from '../../../assets/javascript/midwest/map_providers.js';
3
+
4
+ const LEAFLET_TILES = {
5
+ MutedStandard: {
6
+ url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
7
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
8
+ },
9
+ Standard: {
10
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
11
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
12
+ maxZoom: 19
13
+ },
14
+ Hybrid: {
15
+ url: "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
16
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
17
+ },
18
+ Satellite: {
19
+ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
20
+ attribution: "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community",
21
+ maxZoom: 18
22
+ },
23
+ Dark: {
24
+ url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
25
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
26
+ }
27
+ };
28
+ const GOOGLE_MUTED_STYLES = [
29
+ { elementType: "geometry", stylers: [{ color: "#f5f5f5" }] },
30
+ { elementType: "labels.icon", stylers: [{ visibility: "off" }] },
31
+ { elementType: "labels.text.fill", stylers: [{ color: "#616161" }] },
32
+ { elementType: "labels.text.stroke", stylers: [{ color: "#f5f5f5" }] },
33
+ { featureType: "administrative.land_parcel", elementType: "labels", stylers: [{ visibility: "off" }] },
34
+ { featureType: "poi", elementType: "geometry", stylers: [{ color: "#eeeeee" }] },
35
+ { featureType: "poi.park", elementType: "geometry", stylers: [{ color: "#e5e5e5" }] },
36
+ { featureType: "road", elementType: "geometry", stylers: [{ color: "#ffffff" }] },
37
+ { featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#dadada" }] },
38
+ { featureType: "water", elementType: "geometry", stylers: [{ color: "#c9c9c9" }] }
39
+ ];
40
+ const GOOGLE_DARK_STYLES = [
41
+ { elementType: "geometry", stylers: [{ color: "#242f3e" }] },
42
+ { elementType: "labels.text.stroke", stylers: [{ color: "#242f3e" }] },
43
+ { elementType: "labels.text.fill", stylers: [{ color: "#746855" }] },
44
+ { featureType: "administrative.locality", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
45
+ { featureType: "poi", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
46
+ { featureType: "poi.park", elementType: "geometry", stylers: [{ color: "#263c3f" }] },
47
+ { featureType: "poi.park", elementType: "labels.text.fill", stylers: [{ color: "#6b9a76" }] },
48
+ { featureType: "road", elementType: "geometry", stylers: [{ color: "#38414e" }] },
49
+ { featureType: "road", elementType: "geometry.stroke", stylers: [{ color: "#212a37" }] },
50
+ { featureType: "road", elementType: "labels.text.fill", stylers: [{ color: "#9ca5b3" }] },
51
+ { featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#746855" }] },
52
+ { featureType: "road.highway", elementType: "geometry.stroke", stylers: [{ color: "#1f2835" }] },
53
+ { featureType: "road.highway", elementType: "labels.text.fill", stylers: [{ color: "#f3d19c" }] },
54
+ { featureType: "transit", elementType: "geometry", stylers: [{ color: "#2f3948" }] },
55
+ { featureType: "transit.station", elementType: "labels.text.fill", stylers: [{ color: "#d59563" }] },
56
+ { featureType: "water", elementType: "geometry", stylers: [{ color: "#17263c" }] },
57
+ { featureType: "water", elementType: "labels.text.fill", stylers: [{ color: "#515c6d" }] },
58
+ { featureType: "water", elementType: "labels.text.stroke", stylers: [{ color: "#17263c" }] }
59
+ ];
60
+ class Map extends Controller {
61
+ static values = {
62
+ latitude: { type: Number, default: 37.334886 },
63
+ longitude: { type: Number, default: -122.008988 },
64
+ span: { type: Number, default: 0.1 },
65
+ mapType: { type: String, default: "MutedStandard" },
66
+ colorScheme: { type: String, default: "Auto" },
67
+ provider: { type: String, default: "" },
68
+ token: { type: String, default: "" },
69
+ tokenUrl: { type: String, default: "" },
70
+ apiKey: { type: String, default: "" },
71
+ zoom: { type: Number, default: -1 },
72
+ markers: { type: Array, default: [] },
73
+ showsUserLocation: { type: Boolean, default: false },
74
+ showsMapTypeControl: { type: Boolean, default: false },
75
+ showsZoomControl: { type: Boolean, default: true },
76
+ showsCompass: { type: String, default: "Adaptive" },
77
+ rotation: { type: Number, default: 0 }
78
+ };
79
+ mapInstance = null;
80
+ async connect() {
81
+ const provider = this.providerValue;
82
+ if (!provider)
83
+ return;
84
+ try {
85
+ if (provider === "mapkit") {
86
+ await loadMapKitScript();
87
+ this.initMapKit();
88
+ this.initMapKitMap();
89
+ } else if (provider === "google") {
90
+ await loadGoogleMapsScript(this.apiKeyValue);
91
+ this.initGoogleMap();
92
+ } else if (provider === "leaflet") {
93
+ await loadLeafletScript();
94
+ this.initLeafletMap();
95
+ }
96
+ } catch (err) {
97
+ console.error(`[midwest-map] Failed to initialise ${provider} map:`, err);
98
+ }
99
+ }
100
+ disconnect() {
101
+ if (this.mapInstance) {
102
+ if (this.providerValue === "mapkit")
103
+ this.mapInstance.destroy();
104
+ if (this.providerValue === "leaflet")
105
+ this.mapInstance.remove();
106
+ this.mapInstance = null;
107
+ }
108
+ }
109
+ tokenValueChanged() {
110
+ if (this.mapInstance && this.providerValue === "mapkit")
111
+ this.initMapKit();
112
+ }
113
+ // --- MapKit ---
114
+ initMapKit() {
115
+ const mk = window.mapkit;
116
+ const token = this.tokenValue;
117
+ const tokenUrl = this.tokenUrlValue;
118
+ mk.init({
119
+ authorizationCallback: (done) => {
120
+ if (token) {
121
+ done(token);
122
+ } else if (tokenUrl) {
123
+ fetch(tokenUrl).then((r) => r.text()).then(done).catch((err) => console.error("[midwest-map] Token fetch failed:", err));
124
+ }
125
+ },
126
+ language: document.documentElement.lang || "en"
127
+ });
128
+ }
129
+ initMapKitMap() {
130
+ const mk = window.mapkit;
131
+ const region = new mk.CoordinateRegion(
132
+ new mk.Coordinate(this.latitudeValue, this.longitudeValue),
133
+ new mk.CoordinateSpan(this.spanValue, this.spanValue)
134
+ );
135
+ const colorScheme = this.colorSchemeValue === "Auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? mk.Map.ColorSchemes.Dark : mk.Map.ColorSchemes.Light : mk.Map.ColorSchemes[this.colorSchemeValue];
136
+ this.mapInstance = new mk.Map(this.element, {
137
+ region,
138
+ mapType: mk.Map.MapTypes[this.mapTypeValue],
139
+ colorScheme,
140
+ showsUserLocation: this.showsUserLocationValue,
141
+ showsMapTypeControl: this.showsMapTypeControlValue,
142
+ showsZoomControl: this.showsZoomControlValue,
143
+ showsCompass: mk.FeatureVisibility[this.showsCompassValue],
144
+ rotation: this.rotationValue
145
+ });
146
+ this.addMapKitMarkers();
147
+ this.element.dataset.mapLoaded = "true";
148
+ }
149
+ addMapKitMarkers() {
150
+ const mk = window.mapkit;
151
+ if (!this.markersValue.length)
152
+ return;
153
+ const annotations = this.markersValue.map((m) => {
154
+ const opts = {};
155
+ if (m.title)
156
+ opts.title = m.title;
157
+ if (m.subtitle)
158
+ opts.subtitle = m.subtitle;
159
+ if (m.color)
160
+ opts.color = m.color;
161
+ if (m.glyphText)
162
+ opts.glyphText = m.glyphText;
163
+ return new mk.MarkerAnnotation(
164
+ new mk.Coordinate(m.latitude, m.longitude),
165
+ opts
166
+ );
167
+ });
168
+ this.mapInstance.addAnnotations(annotations);
169
+ const openIndex = this.markersValue.findIndex((m) => m.open);
170
+ if (openIndex >= 0) {
171
+ this.mapInstance.selectedAnnotation = annotations[openIndex];
172
+ }
173
+ }
174
+ // --- Google Maps ---
175
+ initGoogleMap() {
176
+ const g = window.google;
177
+ const zoom = this.zoomValue > 0 ? this.zoomValue : this.spanToZoom(this.spanValue);
178
+ const isDark = this.colorSchemeValue === "Dark" || this.colorSchemeValue === "Auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
179
+ const mapTypeId = this.googleMapTypeId();
180
+ const opts = {
181
+ center: { lat: this.latitudeValue, lng: this.longitudeValue },
182
+ zoom,
183
+ mapTypeId,
184
+ disableDefaultUI: true,
185
+ zoomControl: this.showsZoomControlValue,
186
+ mapTypeControl: this.showsMapTypeControlValue,
187
+ fullscreenControl: false,
188
+ streetViewControl: false
189
+ };
190
+ const styles = this.googleMapStyles(isDark, mapTypeId);
191
+ if (styles)
192
+ opts.styles = styles;
193
+ this.mapInstance = new g.maps.Map(this.element, opts);
194
+ this.addGoogleMarkers();
195
+ this.element.dataset.mapLoaded = "true";
196
+ }
197
+ googleMapTypeId() {
198
+ const types = {
199
+ Standard: "roadmap",
200
+ Hybrid: "hybrid",
201
+ Satellite: "satellite",
202
+ MutedStandard: "roadmap"
203
+ };
204
+ return types[this.mapTypeValue] ?? "roadmap";
205
+ }
206
+ googleMapStyles(isDark, mapTypeId) {
207
+ if (mapTypeId !== "roadmap")
208
+ return null;
209
+ if (isDark)
210
+ return GOOGLE_DARK_STYLES;
211
+ if (this.mapTypeValue === "MutedStandard")
212
+ return GOOGLE_MUTED_STYLES;
213
+ return null;
214
+ }
215
+ addGoogleMarkers() {
216
+ const g = window.google;
217
+ if (!this.markersValue.length)
218
+ return;
219
+ this.markersValue.forEach((m) => {
220
+ const marker = new g.maps.Marker({
221
+ map: this.mapInstance,
222
+ position: { lat: m.latitude, lng: m.longitude },
223
+ title: m.title ?? ""
224
+ });
225
+ if (m.open && m.title) {
226
+ const content = m.subtitle ? `<strong>${m.title}</strong><br><span style="color:#777">${m.subtitle}</span>` : `<strong>${m.title}</strong>`;
227
+ const infoWindow = new g.maps.InfoWindow({ content });
228
+ infoWindow.open(this.mapInstance, marker);
229
+ }
230
+ });
231
+ }
232
+ // --- Leaflet ---
233
+ initLeafletMap() {
234
+ const L = window.L;
235
+ const zoom = this.zoomValue > 0 ? this.zoomValue : this.spanToZoom(this.spanValue);
236
+ const isDark = this.colorSchemeValue === "Dark" || this.colorSchemeValue === "Auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
237
+ const tileKey = isDark ? "Dark" : this.mapTypeValue;
238
+ const tile = LEAFLET_TILES[tileKey] ?? LEAFLET_TILES.MutedStandard;
239
+ this.mapInstance = L.map(this.element, {
240
+ center: [this.latitudeValue, this.longitudeValue],
241
+ zoom,
242
+ zoomControl: this.showsZoomControlValue,
243
+ attributionControl: true
244
+ });
245
+ L.tileLayer(tile.url, {
246
+ attribution: tile.attribution,
247
+ maxZoom: tile.maxZoom ?? 19
248
+ }).addTo(this.mapInstance);
249
+ this.addLeafletMarkers();
250
+ this.element.dataset.mapLoaded = "true";
251
+ }
252
+ addLeafletMarkers() {
253
+ const L = window.L;
254
+ if (!this.markersValue.length)
255
+ return;
256
+ this.markersValue.forEach((m) => {
257
+ const icon = this.leafletMarkerIcon(m.color, m.glyphText);
258
+ const marker = L.marker([m.latitude, m.longitude], { icon }).addTo(this.mapInstance);
259
+ if (m.title) {
260
+ const content = m.subtitle ? `<strong>${m.title}</strong><br><span style="color:#777">${m.subtitle}</span>` : `<strong>${m.title}</strong>`;
261
+ marker.bindPopup(content);
262
+ if (m.open)
263
+ marker.openPopup();
264
+ }
265
+ });
266
+ }
267
+ // Returns a Leaflet icon — default blue when no color/glyph, otherwise a
268
+ // custom SVG pin rendered via divIcon.
269
+ leafletMarkerIcon(color, glyphText) {
270
+ const L = window.L;
271
+ if (!color && !glyphText)
272
+ return new L.Icon.Default();
273
+ const fill = color ?? "#2563eb";
274
+ const inner = glyphText ? `<text x="12" y="16" text-anchor="middle" font-size="9" font-family="system-ui,sans-serif" fill="white">${glyphText}</text>` : `<circle cx="12" cy="12" r="4" fill="rgba(255,255,255,0.9)"/>`;
275
+ const svg = [
276
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 36" width="24" height="36">',
277
+ ` <path d="M12 0C5.373 0 0 5.373 0 12c0 9 12 24 12 24s12-15 12-24C24 5.373 18.627 0 12 0z" fill="${fill}"/>`,
278
+ ` ${inner}`,
279
+ "</svg>"
280
+ ].join("");
281
+ return L.divIcon({
282
+ html: svg,
283
+ className: "",
284
+ iconSize: [24, 36],
285
+ iconAnchor: [12, 36],
286
+ popupAnchor: [0, -36]
287
+ });
288
+ }
289
+ // Approximate MapKit coordinate span to a Web Mercator zoom level.
290
+ // CoordinateSpan 360° = zoom 0; halving span adds 1 zoom level.
291
+ spanToZoom(span) {
292
+ return Math.round(Math.log2(360 / span));
293
+ }
294
+ }
295
+
296
+ export { Map as default };
297
+ //# sourceMappingURL=map_component_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map_component_controller.js","sources":["../../../../../../../app/components/midwest/map_component/map_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\nimport { loadMapKitScript, loadGoogleMapsScript, loadLeafletScript } from '../../../assets/javascript/midwest/map_providers'\n\n// Leaflet tile layers keyed by Midwest map type (MapKit-style strings) and a\n// special 'Dark' entry for dark color schemes. CartoDB and ESRI tiles are free\n// for open-source / low-traffic use; no API key required.\nconst LEAFLET_TILES: Record<string, { url: string; attribution: string; maxZoom?: number }> = {\n MutedStandard: {\n url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',\n attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors &copy; <a href=\"https://carto.com/attributions\">CARTO</a>'\n },\n Standard: {\n url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',\n attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors',\n maxZoom: 19\n },\n Hybrid: {\n url: 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png',\n attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors &copy; <a href=\"https://carto.com/attributions\">CARTO</a>'\n },\n Satellite: {\n url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',\n attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',\n maxZoom: 18\n },\n Dark: {\n url: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',\n attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors &copy; <a href=\"https://carto.com/attributions\">CARTO</a>'\n }\n}\n\n// Muted (light) style for Google Maps — mirrors MapKit's MutedStandard.\nconst GOOGLE_MUTED_STYLES = [\n { elementType: 'geometry', stylers: [{ color: '#f5f5f5' }] },\n { elementType: 'labels.icon', stylers: [{ visibility: 'off' }] },\n { elementType: 'labels.text.fill', stylers: [{ color: '#616161' }] },\n { elementType: 'labels.text.stroke', stylers: [{ color: '#f5f5f5' }] },\n { featureType: 'administrative.land_parcel', elementType: 'labels', stylers: [{ visibility: 'off' }] },\n { featureType: 'poi', elementType: 'geometry', stylers: [{ color: '#eeeeee' }] },\n { featureType: 'poi.park', elementType: 'geometry', stylers: [{ color: '#e5e5e5' }] },\n { featureType: 'road', elementType: 'geometry', stylers: [{ color: '#ffffff' }] },\n { featureType: 'road.highway', elementType: 'geometry', stylers: [{ color: '#dadada' }] },\n { featureType: 'water', elementType: 'geometry', stylers: [{ color: '#c9c9c9' }] }\n]\n\n// Google's standard dark theme.\nconst GOOGLE_DARK_STYLES = [\n { elementType: 'geometry', stylers: [{ color: '#242f3e' }] },\n { elementType: 'labels.text.stroke', stylers: [{ color: '#242f3e' }] },\n { elementType: 'labels.text.fill', stylers: [{ color: '#746855' }] },\n { featureType: 'administrative.locality', elementType: 'labels.text.fill', stylers: [{ color: '#d59563' }] },\n { featureType: 'poi', elementType: 'labels.text.fill', stylers: [{ color: '#d59563' }] },\n { featureType: 'poi.park', elementType: 'geometry', stylers: [{ color: '#263c3f' }] },\n { featureType: 'poi.park', elementType: 'labels.text.fill', stylers: [{ color: '#6b9a76' }] },\n { featureType: 'road', elementType: 'geometry', stylers: [{ color: '#38414e' }] },\n { featureType: 'road', elementType: 'geometry.stroke', stylers: [{ color: '#212a37' }] },\n { featureType: 'road', elementType: 'labels.text.fill', stylers: [{ color: '#9ca5b3' }] },\n { featureType: 'road.highway', elementType: 'geometry', stylers: [{ color: '#746855' }] },\n { featureType: 'road.highway', elementType: 'geometry.stroke', stylers: [{ color: '#1f2835' }] },\n { featureType: 'road.highway', elementType: 'labels.text.fill', stylers: [{ color: '#f3d19c' }] },\n { featureType: 'transit', elementType: 'geometry', stylers: [{ color: '#2f3948' }] },\n { featureType: 'transit.station', elementType: 'labels.text.fill', stylers: [{ color: '#d59563' }] },\n { featureType: 'water', elementType: 'geometry', stylers: [{ color: '#17263c' }] },\n { featureType: 'water', elementType: 'labels.text.fill', stylers: [{ color: '#515c6d' }] },\n { featureType: 'water', elementType: 'labels.text.stroke', stylers: [{ color: '#17263c' }] }\n]\n\n\nexport default class Map extends Controller<HTMLElement> {\n static values = {\n latitude: { type: Number, default: 37.334886 },\n longitude: { type: Number, default: -122.008988 },\n span: { type: Number, default: 0.1 },\n mapType: { type: String, default: 'MutedStandard' },\n colorScheme: { type: String, default: 'Auto' },\n provider: { type: String, default: '' },\n token: { type: String, default: '' },\n tokenUrl: { type: String, default: '' },\n apiKey: { type: String, default: '' },\n zoom: { type: Number, default: -1 },\n markers: { type: Array, default: [] },\n showsUserLocation: { type: Boolean, default: false },\n showsMapTypeControl: { type: Boolean, default: false },\n showsZoomControl: { type: Boolean, default: true },\n showsCompass: { type: String, default: 'Adaptive' },\n rotation: { type: Number, default: 0 }\n }\n\n declare latitudeValue: number\n declare longitudeValue: number\n declare spanValue: number\n declare mapTypeValue: string\n declare colorSchemeValue: string\n declare providerValue: string\n declare tokenValue: string\n declare tokenUrlValue: string\n declare apiKeyValue: string\n declare zoomValue: number\n declare markersValue: {\n latitude: number\n longitude: number\n title?: string\n subtitle?: string\n color?: string\n glyphText?: string\n /** When true, the callout / info window is shown on first load. */\n open?: boolean\n }[]\n\n declare showsUserLocationValue: boolean\n declare showsMapTypeControlValue: boolean\n declare showsZoomControlValue: boolean\n declare showsCompassValue: string\n declare rotationValue: number\n\n private mapInstance: any = null\n\n async connect (): Promise<void> {\n const provider = this.providerValue\n if (!provider) return\n\n try {\n if (provider === 'mapkit') {\n await loadMapKitScript()\n this.initMapKit()\n this.initMapKitMap()\n } else if (provider === 'google') {\n await loadGoogleMapsScript(this.apiKeyValue)\n this.initGoogleMap()\n } else if (provider === 'leaflet') {\n await loadLeafletScript()\n this.initLeafletMap()\n }\n } catch (err) {\n console.error(`[midwest-map] Failed to initialise ${provider} map:`, err)\n }\n }\n\n disconnect (): void {\n if (this.mapInstance) {\n if (this.providerValue === 'mapkit') this.mapInstance.destroy()\n if (this.providerValue === 'leaflet') this.mapInstance.remove()\n this.mapInstance = null\n }\n }\n\n tokenValueChanged (): void {\n if (this.mapInstance && this.providerValue === 'mapkit') this.initMapKit()\n }\n\n // --- MapKit ---\n\n private initMapKit (): void {\n const mk = (window as any).mapkit\n const token = this.tokenValue\n const tokenUrl = this.tokenUrlValue\n\n mk.init({\n authorizationCallback: (done: (t: string) => void) => {\n if (token) {\n done(token)\n } else if (tokenUrl) {\n fetch(tokenUrl)\n .then(r => r.text())\n .then(done)\n .catch(err => console.error('[midwest-map] Token fetch failed:', err))\n }\n },\n language: document.documentElement.lang || 'en'\n })\n }\n\n private initMapKitMap (): void {\n const mk = (window as any).mapkit\n\n const region = new mk.CoordinateRegion(\n new mk.Coordinate(this.latitudeValue, this.longitudeValue),\n new mk.CoordinateSpan(this.spanValue, this.spanValue)\n )\n\n const colorScheme = this.colorSchemeValue === 'Auto'\n ? (window.matchMedia('(prefers-color-scheme: dark)').matches\n ? mk.Map.ColorSchemes.Dark\n : mk.Map.ColorSchemes.Light)\n : mk.Map.ColorSchemes[this.colorSchemeValue]\n\n this.mapInstance = new mk.Map(this.element, {\n region,\n mapType: mk.Map.MapTypes[this.mapTypeValue],\n colorScheme,\n showsUserLocation: this.showsUserLocationValue,\n showsMapTypeControl: this.showsMapTypeControlValue,\n showsZoomControl: this.showsZoomControlValue,\n showsCompass: mk.FeatureVisibility[this.showsCompassValue],\n rotation: this.rotationValue\n })\n\n this.addMapKitMarkers()\n this.element.dataset.mapLoaded = 'true'\n }\n\n private addMapKitMarkers (): void {\n const mk = (window as any).mapkit\n if (!this.markersValue.length) return\n\n const annotations = this.markersValue.map(m => {\n const opts: Record<string, any> = {}\n if (m.title) opts.title = m.title\n if (m.subtitle) opts.subtitle = m.subtitle\n if (m.color) opts.color = m.color\n if (m.glyphText) opts.glyphText = m.glyphText\n\n return new mk.MarkerAnnotation(\n new mk.Coordinate(m.latitude, m.longitude),\n opts\n )\n })\n\n this.mapInstance.addAnnotations(annotations)\n\n // Select the first marker flagged as open so its callout shows on load.\n const openIndex = this.markersValue.findIndex(m => m.open)\n if (openIndex >= 0) {\n this.mapInstance.selectedAnnotation = annotations[openIndex]\n }\n }\n\n // --- Google Maps ---\n\n private initGoogleMap (): void {\n const g = (window as any).google\n const zoom = this.zoomValue > 0 ? this.zoomValue : this.spanToZoom(this.spanValue)\n\n const isDark = this.colorSchemeValue === 'Dark' ||\n (this.colorSchemeValue === 'Auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)\n\n const mapTypeId = this.googleMapTypeId()\n\n const opts: Record<string, any> = {\n center: { lat: this.latitudeValue, lng: this.longitudeValue },\n zoom,\n mapTypeId,\n disableDefaultUI: true,\n zoomControl: this.showsZoomControlValue,\n mapTypeControl: this.showsMapTypeControlValue,\n fullscreenControl: false,\n streetViewControl: false\n }\n\n const styles = this.googleMapStyles(isDark, mapTypeId)\n if (styles) opts.styles = styles\n\n this.mapInstance = new g.maps.Map(this.element, opts)\n this.addGoogleMarkers()\n this.element.dataset.mapLoaded = 'true'\n }\n\n private googleMapTypeId (): string {\n const types: Record<string, string> = {\n Standard: 'roadmap',\n Hybrid: 'hybrid',\n Satellite: 'satellite',\n MutedStandard: 'roadmap'\n }\n return types[this.mapTypeValue] ?? 'roadmap'\n }\n\n private googleMapStyles (isDark: boolean, mapTypeId: string): object[] | null {\n if (mapTypeId !== 'roadmap') return null\n if (isDark) return GOOGLE_DARK_STYLES\n if (this.mapTypeValue === 'MutedStandard') return GOOGLE_MUTED_STYLES\n return null\n }\n\n private addGoogleMarkers (): void {\n const g = (window as any).google\n if (!this.markersValue.length) return\n\n this.markersValue.forEach(m => {\n const marker = new g.maps.Marker({\n map: this.mapInstance,\n position: { lat: m.latitude, lng: m.longitude },\n title: m.title ?? ''\n })\n\n if (m.open && m.title) {\n const content = m.subtitle\n ? `<strong>${m.title}</strong><br><span style=\"color:#777\">${m.subtitle}</span>`\n : `<strong>${m.title}</strong>`\n const infoWindow = new g.maps.InfoWindow({ content })\n infoWindow.open(this.mapInstance, marker)\n }\n })\n }\n\n // --- Leaflet ---\n\n private initLeafletMap (): void {\n const L = (window as any).L\n const zoom = this.zoomValue > 0 ? this.zoomValue : this.spanToZoom(this.spanValue)\n\n const isDark = this.colorSchemeValue === 'Dark' ||\n (this.colorSchemeValue === 'Auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)\n\n const tileKey = isDark ? 'Dark' : this.mapTypeValue\n const tile = LEAFLET_TILES[tileKey] ?? LEAFLET_TILES.MutedStandard\n\n this.mapInstance = L.map(this.element, {\n center: [this.latitudeValue, this.longitudeValue],\n zoom,\n zoomControl: this.showsZoomControlValue,\n attributionControl: true\n })\n\n L.tileLayer(tile.url, {\n attribution: tile.attribution,\n maxZoom: tile.maxZoom ?? 19\n }).addTo(this.mapInstance)\n\n this.addLeafletMarkers()\n this.element.dataset.mapLoaded = 'true'\n }\n\n private addLeafletMarkers (): void {\n const L = (window as any).L\n if (!this.markersValue.length) return\n\n this.markersValue.forEach(m => {\n const icon = this.leafletMarkerIcon(m.color, m.glyphText)\n const marker = L.marker([m.latitude, m.longitude], { icon })\n .addTo(this.mapInstance)\n\n if (m.title) {\n const content = m.subtitle\n ? `<strong>${m.title}</strong><br><span style=\"color:#777\">${m.subtitle}</span>`\n : `<strong>${m.title}</strong>`\n marker.bindPopup(content)\n if (m.open) marker.openPopup()\n }\n })\n }\n\n // Returns a Leaflet icon — default blue when no color/glyph, otherwise a\n // custom SVG pin rendered via divIcon.\n private leafletMarkerIcon (color?: string, glyphText?: string): any {\n const L = (window as any).L\n if (!color && !glyphText) return new L.Icon.Default()\n\n const fill = color ?? '#2563eb'\n const inner = glyphText\n ? `<text x=\"12\" y=\"16\" text-anchor=\"middle\" font-size=\"9\" font-family=\"system-ui,sans-serif\" fill=\"white\">${glyphText}</text>`\n : `<circle cx=\"12\" cy=\"12\" r=\"4\" fill=\"rgba(255,255,255,0.9)\"/>`\n\n const svg = [\n '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 36\" width=\"24\" height=\"36\">',\n ` <path d=\"M12 0C5.373 0 0 5.373 0 12c0 9 12 24 12 24s12-15 12-24C24 5.373 18.627 0 12 0z\" fill=\"${fill}\"/>`,\n ` ${inner}`,\n '</svg>'\n ].join('')\n\n return L.divIcon({\n html: svg,\n className: '',\n iconSize: [24, 36] as [number, number],\n iconAnchor: [12, 36] as [number, number],\n popupAnchor: [0, -36] as [number, number]\n })\n }\n\n // Approximate MapKit coordinate span to a Web Mercator zoom level.\n // CoordinateSpan 360° = zoom 0; halving span adds 1 zoom level.\n private spanToZoom (span: number): number {\n return Math.round(Math.log2(360 / span))\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ import { observeViewport } from '../../../assets/javascript/midwest/viewport_observer.js';
3
+
4
+ class Motion extends Controller {
5
+ static values = {
6
+ threshold: { type: Number, default: 0.15 },
7
+ once: { type: Boolean, default: true }
8
+ };
9
+ teardown = null;
10
+ connect() {
11
+ this.teardown = observeViewport(this.element, {
12
+ threshold: this.thresholdValue,
13
+ once: this.onceValue,
14
+ onEnter: () => this.reveal(),
15
+ onLeave: this.onceValue ? void 0 : () => this.reset()
16
+ });
17
+ }
18
+ disconnect() {
19
+ this.teardown?.();
20
+ this.teardown = null;
21
+ }
22
+ reveal() {
23
+ this.element.setAttribute("data-motion-visible", "");
24
+ this.dispatch("reveal", { bubbles: true });
25
+ }
26
+ reset() {
27
+ this.element.removeAttribute("data-motion-visible");
28
+ }
29
+ }
30
+
31
+ export { Motion as default };
32
+ //# sourceMappingURL=motion_component_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion_component_controller.js","sources":["../../../../../../../app/components/midwest/motion_component/motion_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\nimport { observeViewport } from '../../../assets/javascript/midwest/viewport_observer'\n\n// Reveal motion primitives — the `trigger: :reveal` path. Reveals the element\n// when it scrolls into view by flipping a single `data-motion-visible` attribute\n// that CSS transitions animate against; no bespoke JavaScript in the host app.\n//\n// The `trigger: :load` path needs no controller at all — it animates in via a\n// pure-CSS `@starting-style` entry — so this controller is only attached to\n// `:reveal` elements.\n//\n// Values\n// threshold — fraction of the element that must be visible to reveal (0–1)\n// once — stop observing after the first reveal; when false, the element\n// resets as it leaves the viewport so the motion can replay\nexport default class Motion extends Controller<HTMLElement> {\n static values = {\n threshold: { type: Number, default: 0.15 },\n once: { type: Boolean, default: true }\n }\n\n declare thresholdValue: number\n declare onceValue: boolean\n\n private teardown: (() => void) | null = null\n\n connect () {\n this.teardown = observeViewport(this.element, {\n threshold: this.thresholdValue,\n once: this.onceValue,\n onEnter: () => this.reveal(),\n onLeave: this.onceValue ? undefined : () => this.reset()\n })\n }\n\n disconnect () {\n this.teardown?.()\n this.teardown = null\n }\n\n private reveal () {\n this.element.setAttribute('data-motion-visible', '')\n this.dispatch('reveal', { bubbles: true })\n }\n\n private reset () {\n this.element.removeAttribute('data-motion-visible')\n }\n}\n"],"names":[],"mappings":";;;AAeA,MAAqB,eAAe,UAAwB,CAAA;AAAA,EAC1D,OAAO,MAAS,GAAA;AAAA,IACd,SAAW,EAAA,EAAE,IAAM,EAAA,MAAA,EAAQ,SAAS,IAAK,EAAA;AAAA,IACzC,IAAM,EAAA,EAAE,IAAM,EAAA,OAAA,EAAS,SAAS,IAAK,EAAA;AAAA,GACvC,CAAA;AAAA,EAKQ,QAAgC,GAAA,IAAA,CAAA;AAAA,EAExC,OAAW,GAAA;AACT,IAAK,IAAA,CAAA,QAAA,GAAW,eAAgB,CAAA,IAAA,CAAK,OAAS,EAAA;AAAA,MAC5C,WAAW,IAAK,CAAA,cAAA;AAAA,MAChB,MAAM,IAAK,CAAA,SAAA;AAAA,MACX,OAAA,EAAS,MAAM,IAAA,CAAK,MAAO,EAAA;AAAA,MAC3B,SAAS,IAAK,CAAA,SAAA,GAAY,KAAY,CAAA,GAAA,MAAM,KAAK,KAAM,EAAA;AAAA,KACxD,CAAA,CAAA;AAAA,GACH;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,QAAW,IAAA,CAAA;AAChB,IAAA,IAAA,CAAK,QAAW,GAAA,IAAA,CAAA;AAAA,GAClB;AAAA,EAEQ,MAAU,GAAA;AAChB,IAAK,IAAA,CAAA,OAAA,CAAQ,YAAa,CAAA,qBAAA,EAAuB,EAAE,CAAA,CAAA;AACnD,IAAA,IAAA,CAAK,QAAS,CAAA,QAAA,EAAU,EAAE,OAAA,EAAS,MAAM,CAAA,CAAA;AAAA,GAC3C;AAAA,EAEQ,KAAS,GAAA;AACf,IAAK,IAAA,CAAA,OAAA,CAAQ,gBAAgB,qBAAqB,CAAA,CAAA;AAAA,GACpD;AACF;;;;"}