Skip to content

Commit

Permalink
Merge branch 'release/v0.10.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
holtwick committed Mar 7, 2024
2 parents 98c319d + e223501 commit c9df46e
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 26 deletions.
2 changes: 1 addition & 1 deletion lib/float/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export { default as OuiMenu } from './oui-menu.vue'
export { default as OuiMenuItems } from './oui-menu-items.vue'
export { default as OuiTooltipActivator } from './oui-tooltip-activator.vue'

export { vMenu, useMenu } from './use-menu'
export { vMenu, useMenu, useMenuWithValue } from './use-menu'

// export { vActionToggle } from './lib'
52 changes: 33 additions & 19 deletions lib/float/use-menu.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import type { DirectiveBinding } from 'vue'
import { isRecord } from 'zeed'
import type { LoggerInterface } from 'zeed'
import { Logger, isRecord } from 'zeed'
import OuiMenu from './oui-menu.vue'
import type { OuiMenuItem } from './_types'
import { mountComponentAsApp } from './app-helper'

const log: LoggerInterface = Logger('use-menu')

type OuiMenuCreator = (...args: any) => OuiMenuItem[]
type OuiMenuItemSource = (OuiMenuItem | false | undefined | null)[] | OuiMenuCreator

function generateGetBoundingClientRect(x = 0, y = 0) {
return () => ({
Expand All @@ -17,35 +21,39 @@ function generateGetBoundingClientRect(x = 0, y = 0) {
} as DOMRect)
}

function isSeparator(item: any) {
return item === null || (isRecord(item) && Object.keys(item).length === 0)
}

/**
* Context menu emulation.
*
* If triggered by BUTTON it will show as a drop down, else close to the mouse position.
*/
export function useMenu(itemsSource: (OuiMenuItem | false | undefined | null)[] | OuiMenuCreator) {
export function useMenu(itemsSource: OuiMenuItemSource) {
let app: any

// todo this would require to use it top level always
// onBeforeUnmount(() => app?.done())

return (...args: any) => {
// log('useMenu trigger', args)
log('useMenu trigger', args)

// Find and handle event
const event = args.find((a: any) => a instanceof Event) as MouseEvent
const { clientX: x, clientY: y, target } = event

let cursor: HTMLElement | null = target as any
let cursorElement: HTMLElement | null = target as any

let reference: HTMLElement | undefined | null

// Uses containing BUTTON if available
while (cursor) {
if (cursor.tagName?.toUpperCase() === 'BUTTON') {
reference = cursor
while (cursorElement) {
if (cursorElement.tagName?.toUpperCase() === 'BUTTON') {
reference = cursorElement
break
}
cursor = cursor.parentElement
cursorElement = cursorElement.parentElement
}

if (reference == null && target != null) {
Expand All @@ -64,18 +72,14 @@ export function useMenu(itemsSource: (OuiMenuItem | false | undefined | null)[]
const items = (dynamic ? itemsSource(...args) : itemsSource).filter(item => item != null && item !== false)

// Cleanup separators at ends and multiple
const isSeparator = (item: any) => item === null || (isRecord(item) && Object.keys(item).length === 0)

// log('items', JSON.stringify(items, null, 2))

for (let i = items.length - 1; i >= 0; i--) {
if (isSeparator(items[i])) {
if (i === 0 || i === items.length - 1 || isSeparator(items[i - 1]) || isSeparator(items[i + 1]))
items.splice(i, 1)
}
}

// log('items', items)
log('items', items)

if (items.length <= 0)
return
Expand All @@ -90,24 +94,34 @@ export function useMenu(itemsSource: (OuiMenuItem | false | undefined | null)[]
// app.awaitDone.then(() => (app = undefined))
}
else {
// log.warn('useMenu target missing')
log.warn('useMenu target missing')
}
}
}

export function useMenuWithValue<T = any>(itemsSource: OuiMenuCreator) {
const menu = useMenu(itemsSource)
return (value: T) => {
return (...args: any) => menu(value, ...args)
}
}

// export function menuWithArgs(value: any) {
// return (...args: any) => menu(value, ...args)
// }

/** Vue3 Directive! */
export const vMenu = {
mounted: (element: HTMLElement, binding: DirectiveBinding) => {
// log("v-menu", el, binding)
// log.assert(typeof fn === 'function', 'v-menu requires function as argument')
log('v-menu', element, binding)
log.assert(typeof binding.value === 'function', 'v-menu requires function as argument')
element.addEventListener('contextmenu', (event: MouseEvent) => {
// log('v-menu context')
log('v-menu context')
event.preventDefault() // no system menu
binding.value(event, element)
})

element.addEventListener('click', (event: MouseEvent) => {
// log('v-menu click')
log('v-menu click')
binding.value(event, element)
})
},
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "oui-kit",
"type": "module",
"version": "0.10.5",
"version": "0.10.6",
"author": {
"name": "Dirk Holtwick",
"email": "[email protected]",
Expand Down Expand Up @@ -69,7 +69,7 @@
"lint:fix": "eslint . --fix",
"prepublishOnly": "nr build",
"preview": "vite preview",
"start": "nr dev",
"start": "nr story:dev",
"story:build": "histoire build",
"story:dev": "histoire dev",
"story:preview": "histoire preview",
Expand All @@ -88,24 +88,24 @@
"@tsconfig/node18": "^18.2.2",
"@types/jest": "^29.5.12",
"@types/jsdom": "^21.1.6",
"@types/node": "^20.11.24",
"@types/node": "^20.11.25",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/test-utils": "^2.4.4",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"fast-glob": "^3.3.2",
"histoire": "^0.17.9",
"jsdom": "^24.0.0",
"lucide-vue-next": "^0.344.0",
"lucide-vue-next": "^0.350.0",
"npm-run-all2": "^6.1.2",
"only-allow": "^1.2.1",
"sort-package-json": "^2.8.0",
"stylus": "^0.63.0",
"tsup": "^8.0.2",
"typescript": "^5.3.3",
"typescript": "^5.4.2",
"vite": "^5.1.5",
"vite-plugin-dts": "^3.7.3",
"vitest": "^1.3.1",
"vue-tsc": "^2.0.4"
"vue-tsc": "^2.0.6"
}
}
65 changes: 65 additions & 0 deletions stories/OuiMenu.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<script lang="ts" setup>
import { logEvent } from 'histoire/client'
import { useMenu, useMenuWithValue, vMenu } from '@/lib'
function initialState() {
return {
}
}
const simpleMenu = useMenu([
{
title: `ABC`,
action: self => logEvent('action', self),
},
{},
{
title: `XYZ`,
action: self => logEvent('action', self),
},
])
const menu = useMenuWithValue((item: number) => [{
title: `Hello ${item}`,
action: () => {
logEvent('hallo', { item })
},
}])
</script>

<template>
<Story auto-props-disabled>
<template #controls="{ state }">
<HstCheckbox v-model="state.show" title="show" />
</template>
<Variant title="useMenu" :init-state="initialState">
<template #default>
<button ref="button" v-menu="simpleMenu">
Click me
</button>
<p>You may click with right or left mouse key.</p>
</template>
</Variant>
<Variant title="useMenuWithValue" :init-state="initialState">
<template #default>
<button ref="button" v-menu="menu(1)">
Click 1
</button>
<button ref="button" v-menu="menu(2)">
Click 2
</button>
<p>You may click with right or left mouse key.</p>
</template>
</Variant>
</story>
</template>

<docs lang="md">
# useMenu

Dynamic menu generation.

# v-menu

Directive to assign a menu to a HTML element.
</docs>

0 comments on commit c9df46e

Please sign in to comment.