Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e14ded9
feat(iconMenu): `menus` should be mutable
oeninghe-dataport Jul 6, 2026
8f36876
test(snowbox): allow removal and live insertion of iconMenu plugins
oeninghe-dataport Jul 6, 2026
4888d29
fix(filter): reset filter state on plugin removal
oeninghe-dataport Jul 6, 2026
866b395
Merge branch 'next' into fix/668-iconMenu-plugin-removal
oeninghe-dataport Jul 29, 2026
d7e23ae
refactor(iconMenu): extract lib function watchArray
oeninghe-dataport Jul 29, 2026
b47d6cf
feat(iconMenu): allow adding and removal of plugins
oeninghe-dataport Jul 29, 2026
28a8ef6
refactor(iconMenu): replace watcher with explicit mutations
oeninghe-dataport Jul 29, 2026
bb128d2
test(snowbox): adapt new iconMenu modification
oeninghe-dataport Jul 29, 2026
1d136e7
refactor(iconMenu): replace open/focusOpen watcher with mutations
oeninghe-dataport Jul 29, 2026
fe13760
fix: remove unused watchArray lib function
oeninghe-dataport Aug 3, 2026
d2b0008
refactor(iconMenu): simplify store action calls
oeninghe-dataport Aug 3, 2026
ee8f935
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
962aa61
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
441b0be
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
42c1204
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
b6d0d30
docs(iconMenu): fix maturity
oeninghe-dataport Aug 3, 2026
7b57647
fix(iconMenu): use `toRaw`, better safe than sorry
oeninghe-dataport Aug 3, 2026
b44a099
Merge branch 'next' into fix/668-iconMenu-plugin-removal
oeninghe-dataport Aug 3, 2026
7bbdec2
perf: reduce store calls when plugin is removed
oeninghe-dataport Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/snowbox/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ <h1>POLAR map client</h1>
</select>
</label>
<button id="color-scheme-switcher">Switch to dark mode</button>
<button id="toggle-filter">Add/Remove filter plugin</button>
<div>
Coordinates of currently selected feature: <span id="selected-feature-coordinates"></span>
</div>
Expand Down
19 changes: 17 additions & 2 deletions examples/snowbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
colorScheme,
startCenter: [565874, 5934140],
layers: [
// TODO: Add internalization to snowbox

Check warning on line 96 in examples/snowbox/index.js

View workflow job for this annotation

GitHub Actions / Linting

Unexpected 'todo' comment: 'TODO: Add internalization to snowbox'
{
id: basemapId,
visibility: true,
Expand Down Expand Up @@ -394,6 +394,7 @@
layoutTag: 'BOTTOM_LEFT',
})
)
let filterItem
addPlugin(
map,
pluginIconMenu({
Expand All @@ -409,7 +410,7 @@
plugin: pluginLayerChooser({}),
},
],
[
(filterItem = [
{
plugin: pluginFilter({
layers: {
Expand Down Expand Up @@ -473,7 +474,7 @@
},
}),
},
],
]),
[
{
plugin: pluginGeoLocation({
Expand Down Expand Up @@ -583,3 +584,17 @@
colorScheme = colorScheme === 'light' ? 'dark' : 'light'
updateState(map, 'core', 'colorScheme', colorScheme)
})

let hasFilter = true
document
.getElementById('toggle-filter')
.addEventListener('click', ({ target }) => {
const store = getStore(map, 'iconMenu')
if (hasFilter) {
store.removePlugin('filter')
hasFilter = false
} else {
store.addPlugin(filterItem)
hasFilter = true
}
})
69 changes: 69 additions & 0 deletions src/lib/watchArray.ts
Comment thread
dopenguin marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { WatchOptions, WatchSource } from 'vue'

import { watch } from 'vue'

function getDifference<T>(items: T[], otherItems: T[]) {
const unmatchedItems = [...otherItems]
return items.filter((item) => {
const matchIndex = unmatchedItems.findIndex((otherItem) =>
Object.is(item, otherItem)
)
if (matchIndex === -1) {
return true
}
unmatchedItems.splice(matchIndex, 1)
return false
})
}

/**
* Watches an array and invokes callbacks for every added and removed item.
*
* @param source - Reactive array source to watch
* @param onAdded - Callback invoked for every added item
* @param onRemoved - Callback invoked for every removed item
* @param options - Vue watch options
*/
export function watchArray<T>(
source: WatchSource<T[]>,
onAdded: (item: T) => void,
onRemoved: (item: T) => void,
options?: WatchOptions
) {
return watch(
source,
(newItems, oldItems) => {
const previousItems = oldItems ?? []
getDifference(newItems, previousItems).forEach((item) => {
onAdded(item)
})
getDifference(previousItems, newItems).forEach((item) => {
onRemoved(item)
})
},
options
)
}

if (import.meta.vitest) {
const { expect, test, vi } = import.meta.vitest
const { ref } = await import('vue')

test('calls the respective callback for each added and removed item', () => {
const items = ref([{ id: 1 }, { id: 2 }])
const onAdded = vi.fn()
const onRemoved = vi.fn()

watchArray(items, onAdded, onRemoved, { flush: 'sync' })

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const retainedItem = items.value[1]!
const addedItem = { id: 3 }
items.value = [retainedItem, addedItem]

expect(onAdded).toHaveBeenCalledOnce()
expect(onAdded).toHaveBeenCalledWith(addedItem)
expect(onRemoved).toHaveBeenCalledOnce()
expect(onRemoved).toHaveBeenCalledWith({ id: 1 })
})
}
2 changes: 2 additions & 0 deletions src/plugins/filter/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ export const useFilterStore = defineStore('plugins/filter', () => {
{ deep: true, immediate: true }
)
)
teardownCallbacks.push(callback)
Comment thread
dopenguin marked this conversation as resolved.
})
}

function teardownPlugin() {
filterMainStore.state = {}
teardownCallbacks.forEach((callback) => {
callback()
})
Expand Down
7 changes: 2 additions & 5 deletions src/plugins/iconMenu/components/NineRegionsButton.ce.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { storeToRefs } from 'pinia'
import { computed, inject } from 'vue'

import PolarIconButton from '@/components/PolarIconButton.ce.vue'
import { useCoreStore } from '@/core/stores'

import { useIconMenuStore } from '../store'

Expand All @@ -31,11 +30,9 @@ const updateMaxWidth = inject('updateMaxWidth') as () => void

function toggle() {
if (open.value === props.id) {
open.value = null
useCoreStore().setMoveHandle(null)
iconMenuStore.openMenuById(null)
} else {
open.value = props.id
iconMenuStore.openInMoveHandle(props.id)
iconMenuStore.openMenuById(props.id)
}
Comment thread
dopenguin marked this conversation as resolved.
Outdated
updateMaxWidth()
}
Expand Down
6 changes: 2 additions & 4 deletions src/plugins/iconMenu/components/StandardFocusMenu.ce.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,14 @@ const maxHeight = computed(() =>

function toggle(id: string) {
if (iconMenuStore.focusOpen === id) {
iconMenuStore.focusOpen = null
iconMenuStore.openFocusMenuById(null)
pluginComponent.value = null
coreStore.setMoveHandle(null)
} else {
iconMenuStore.focusOpen = id
iconMenuStore.openFocusMenuById(id)
pluginComponent.value = markRaw(
(props.menus.find(({ plugin }) => plugin.id === id) as Menu).plugin
.component as Component
)
iconMenuStore.openInMoveHandle(id, true)
}
}
</script>
Expand Down
6 changes: 2 additions & 4 deletions src/plugins/iconMenu/components/StandardMenuList.ce.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,9 @@ function updateMaxWidth() {

function toggle(id: string) {
if (open.value === id) {
open.value = null
coreStore.setMoveHandle(null)
iconMenuStore.openMenuById(null)
} else {
open.value = id
iconMenuStore.openInMoveHandle(id)
iconMenuStore.openMenuById(id)
}
Comment thread
dopenguin marked this conversation as resolved.
Outdated
updateMaxWidth()
}
Expand Down
Loading
Loading