Skip to content

feat(google-maps): OverlayView defaultOpen prop and LatLng position widening#696

Merged
harlan-zw merged 1 commit intomainfrom
feat/google-maps-overlay-view-controlled-api
Apr 9, 2026
Merged

feat(google-maps): OverlayView defaultOpen prop and LatLng position widening#696
harlan-zw merged 1 commit intomainfrom
feat/google-maps-overlay-view-controlled-api

Conversation

@harlan-zw
Copy link
Copy Markdown
Collaborator

🔗 Linked issue

Related to #689 (PR D in the umbrella split-up plan).

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

Two additive changes to <ScriptGoogleMapsOverlayView>:

  1. defaultOpen prop for the uncontrolled mode. The overlay still opens on mount by default; pass :default-open="false" to start closed without taking over the open state via v-model. Bound v-model:open continues to take precedence (when bound, defaultOpen is ignored).
  2. Widened position prop to LatLng | LatLngLiteral. Values returned by Maps APIs (e.g. resolveQueryToLatLng, marker positions) can now be passed straight through without manual conversion.

Both changes are additive and backwards-compatible: existing code that omits defaultOpen and passes a LatLngLiteral keeps working with no behaviour change.

🛠 Implementation notes

  • Refactored to reactive prop destructure with inline defaults; withDefaults removed (matches the project's preferred Vue 3.5 idiom).
  • defineModel is kept for the open model so its default: undefined opts out of Vue's boolean prop coercion. Without that opt-out an unset open would coerce to false and break the uncontrolled default.
  • Added normalizeLatLng helper in useGoogleMapsResource that detects callable .lat/.lng accessors instead of relying on instanceof google.maps.LatLng (mocks in tests return plain objects, so the instanceof check would always be false).
  • The position watcher source is normalized so the watch identity is stable for both LatLng instances and literals.
  • The inline default for defaultOpen is true, preserving v0 behaviour (defineModel('open', { default: undefined }) opens by default).

🛡 Protected behaviours

All v1 protected behaviours are untouched:

  • defineModel('open', { default: undefined }) still treats missing v-model:open as undefined, not false (regression test in google-maps-regressions.test.ts).
  • data-state="open"/"closed" propagation on the overlay element.
  • Marker drag listeners forwarding to draw().
  • Skip-if-equal setCenter watcher and setOptions exclusion of zoom/center on the parent map.

📝 Migration

None required. To opt into the new uncontrolled-closed default:

-<ScriptGoogleMapsOverlayView :position="pos" v-if="false" />
+<ScriptGoogleMapsOverlayView :position="pos" :default-open="false" />

To pass a LatLng value directly:

 const sydney = await mapRef.value?.resolveQueryToLatLng('Sydney, Australia')
-<ScriptGoogleMapsOverlayView :position="{ lat: sydney.lat(), lng: sydney.lng() }" />
+<ScriptGoogleMapsOverlayView :position="sydney" />

🧪 Tests

New file test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts (uses mountSuspended from @nuxt/test-utils/runtime) with:

  • 3 unit tests for normalizeLatLng (literal pass-through, callable accessors, negative coords)
  • 3 uncontrolled-mode tests (default open, defaultOpen=false, defaultOpen=true)
  • 3 controlled-mode tests (:open=true, :open=false, smoke check that mount doesn't emit update:open)
  • 2 position-format tests (LatLngLiteral, LatLng-shaped instance with callable accessors)

The mount tests use a minimal MockOverlayView base class that auto-fires onAdd + draw when setMap is called, mirroring the real Google Maps lifecycle just enough to exercise the SFC's draw path.

All 95 google-maps-* tests pass; lint and typecheck clean.

Docs

Added "Controlled vs Uncontrolled Open State" and "Position Format" sections to docs/content/scripts/google-maps/2.api/11.overlay-view.md.

…idening

Two additive changes to <ScriptGoogleMapsOverlayView>:

1. Add `defaultOpen` prop for the uncontrolled mode. The overlay still
   opens on mount by default; pass `:default-open="false"` to start
   closed without taking control of the open state via v-model. Bound
   v-model:open continues to take precedence.
2. Widen the `position` prop to accept `google.maps.LatLng |
   LatLngLiteral` so values returned by Maps APIs (e.g.
   `resolveQueryToLatLng`, marker positions) can be passed straight
   through without manual conversion.

Implementation notes:

- Refactored to reactive prop destructure with inline defaults
  (matching the project preference); withDefaults removed
- defineModel kept for the `open` model so its `default: undefined`
  opts out of Vue's boolean prop coercion (which would otherwise turn
  an unset `open` into `false` and break the uncontrolled default)
- Added `normalizeLatLng` helper in `useGoogleMapsResource` that
  detects callable `.lat`/`.lng` accessors instead of relying on
  `instanceof google.maps.LatLng` (mocks in tests return plain objects)
- The `props.position` watcher source is normalized so the watch
  identity is stable for both LatLng instances and literals
- Inline default for `defaultOpen` is `true`, preserving v0 behaviour

Docs: added "Controlled vs Uncontrolled Open State" and "Position
Format" sections to overlay-view.md.

Tests: new file `test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts`
covering the uncontrolled default, defaultOpen=false, controlled
explicit :open prop, and both position shapes via mountSuspended with a
minimal mock OverlayView base class. Plus pure-helper tests for
normalizeLatLng.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 9, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/scripts@696

commit: 1429944

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
scripts-playground Ready Ready Preview, Comment Apr 9, 2026 2:22pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This PR introduces support for both google.maps.LatLng and google.maps.LatLngLiteral position types in the ScriptGoogleMapsOverlayView component. A new normalizeLatLng utility function is added to unify handling of both formats at runtime. The component's open-state management is refactored from a simple prop to a controlled/uncontrolled model pattern via defineModel, introducing a new defaultOpen boolean prop for uncontrolled scenarios. Internal prop references are switched to destructured declarations, and watch logic is updated to normalize position values. Documentation and comprehensive test coverage are provided for the new functionality.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Details

Component Changes (ScriptGoogleMapsOverlayView.vue): The shift from withDefaults + direct prop access to defineModel for the open state and destructured props requires careful verification of reactivity semantics and watch dependencies. The introduction of normalizeLatLng in watch logic and the expanded position type signature warrant tracing through position-dependent rendering paths.

Utility Addition (useGoogleMapsResource.ts): The new helper is straightforward—runtime type detection via callable method checking is simple and clear.

Test Coverage (google-maps-overlay-view.nuxt.test.ts): Test suite is substantial and well-structured, covering both helper logic and component lifecycle scenarios (uncontrolled vs. controlled modes). Mocked Google Maps API objects validate position normalization and state initialization paths.

Documentation (11.overlay-view.md): Additions clearly articulate the two open-state patterns and position format compatibility.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the two main changes: adding a defaultOpen prop and widening the position prop to accept LatLng types.
Description check ✅ Passed The description thoroughly explains the changes, implementation approach, protected behaviors, tests, and documentation updates related to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/google-maps-overlay-view-controlled-api

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
docs/content/scripts/google-maps/2.api/11.overlay-view.md (1)

140-152: Code example has variable declared after use.

In the showSydney function, position.value is assigned on line 148, but the position ref is declared on line 151. While Vue's <script setup> hoists declarations, this ordering may confuse readers following the code top-to-bottom.

📝 Suggested reordering for clarity
 <script setup lang="ts">
 const mapRef = ref()
+const position = ref()

 async function showSydney() {
   // Resolve a query into a LatLng-shaped value via the Maps API
   const sydney = await mapRef.value?.resolveQueryToLatLng('Sydney, Australia')
   // Pass it through unchanged: works for both shapes
   position.value = sydney
 }
-
-const position = ref()
 </script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/scripts/google-maps/2.api/11.overlay-view.md` around lines 140 -
152, The example declares position after it's used which can confuse readers;
move the ref declaration for position so it's defined before showSydney (or
before any use), e.g., declare const position = ref() above the showSydney
function (and keep mapRef where it is) so showSydney, mapRef, and position are
in clear top-to-bottom order.
test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts (1)

126-129: Consider adding a comment explaining the triple nextTick.

The three consecutive nextTick() calls are necessary to allow useGoogleMapsResource's whenever() watcher and the overlay's async initialization to complete, but the rationale isn't immediately obvious.

📝 Suggested documentation
   // Allow useGoogleMapsResource's whenever() to fire and the overlay to attach
+  // Multiple ticks needed: (1) whenever() fires, (2) Promise.resolve in create,
+  // (3) Vue's DOM update cycle completes
   await nextTick()
   await nextTick()
   await nextTick()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts` around lines 126 -
129, Add an inline comment above the three consecutive await nextTick() calls in
the test to explain why three ticks are required: that useGoogleMapsResource's
whenever() watcher needs one microtask to run, the overlay's async
initialization needs another, and a final tick ensures the overlay is attached
before assertions; reference the involved symbols nextTick,
useGoogleMapsResource, whenever, and the overlay initialization so future
readers understand the sequencing dependency.
packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue (1)

127-128: Redundant nullish coalescing.

Since defaultOpen has an inline default of true on line 103, the ?? true fallback on line 128 is unnecessary. The only way defaultOpen could be undefined at this point is if the destructuring default somehow failed, which won't happen.

📝 Suggested simplification
 if (open.value === undefined)
-  open.value = defaultOpen ?? true
+  open.value = defaultOpen
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue`
around lines 127 - 128, The nullish coalescing fallback is redundant: in
ScriptGoogleMapsOverlayView.vue the destructured prop/variable defaultOpen
already defaults to true, so update the assignment that sets open.value (the
line using "if (open.value === undefined) open.value = defaultOpen ?? true") to
simply assign defaultOpen (i.e., remove "?? true"); ensure you only change the
assignment expression and leave the open.value undefined check intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/content/scripts/google-maps/2.api/11.overlay-view.md`:
- Around line 140-152: The example declares position after it's used which can
confuse readers; move the ref declaration for position so it's defined before
showSydney (or before any use), e.g., declare const position = ref() above the
showSydney function (and keep mapRef where it is) so showSydney, mapRef, and
position are in clear top-to-bottom order.

In
`@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue`:
- Around line 127-128: The nullish coalescing fallback is redundant: in
ScriptGoogleMapsOverlayView.vue the destructured prop/variable defaultOpen
already defaults to true, so update the assignment that sets open.value (the
line using "if (open.value === undefined) open.value = defaultOpen ?? true") to
simply assign defaultOpen (i.e., remove "?? true"); ensure you only change the
assignment expression and leave the open.value undefined check intact.

In `@test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts`:
- Around line 126-129: Add an inline comment above the three consecutive await
nextTick() calls in the test to explain why three ticks are required: that
useGoogleMapsResource's whenever() watcher needs one microtask to run, the
overlay's async initialization needs another, and a final tick ensures the
overlay is attached before assertions; reference the involved symbols nextTick,
useGoogleMapsResource, whenever, and the overlay initialization so future
readers understand the sequencing dependency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 777ffa0e-9285-4465-94ec-83b476d609b4

📥 Commits

Reviewing files that changed from the base of the PR and between 4f10dd7 and 1429944.

📒 Files selected for processing (4)
  • docs/content/scripts/google-maps/2.api/11.overlay-view.md
  • packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue
  • packages/script/src/runtime/components/GoogleMaps/useGoogleMapsResource.ts
  • test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts

@harlan-zw harlan-zw merged commit 7bfc780 into main Apr 9, 2026
17 checks passed
@harlan-zw harlan-zw deleted the feat/google-maps-overlay-view-controlled-api branch April 9, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant